## Make output directory
outdir <- "~/multiOmic_benchmark/report/output/20191127_tcellTrajectory/"
ifelse(!dir.exists(outdir), dir.create(outdir), FALSE)
[1] FALSE

Based on the results of my benchmark, I set out to align expression and accessibility profiles from the F74 developing thymus dataset to detect changes in accessibility along pseudotime trajectories. While the benchmark was based on the task of label propagation, I here use the two most faithful methods (Seurat CCA and Conos) to achieve a common embedding of ATAC-seq and RNA-seq cells.

Load datasets.

rna.sce <- readRDS("~/my_data/F74_RNA_seurat_processed.RDS")
atac.sce <- readRDS("~/my_data/F74_ATAC_snapAtac_processed_bgmat.RDS")

## Re-normalize RNA data
seu.rna <- as.Seurat(rna.sce, counts = "counts")
seu.rna <- NormalizeData(seu.rna)
Performing log-normalization
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
logcounts(rna.sce) <- seu.rna@assays$RNA@data

Filter genes with zero variance

rna.gene.var <- as.matrix(counts(rna.sce)) %>% rowVars()
atac.gene.var <- as.matrix(counts(atac.sce)) %>% rowVars()

rna.sce <- rna.sce[which(rna.gene.var > 0),]
atac.sce <- atac.sce[which(atac.gene.var > 0),]

rna.sce; atac.sce
class: SingleCellExperiment 
dim: 24510 8321 
metadata(0):
assays(3): counts cpm logcounts
rownames(24510): RP11-34P13.3 RP11-34P13.7 ... AC233755.1 AC240274.1
rowData names(0):
colnames(8321): AAACCTGAGTTCGATC_1 AAACCTGCAAGTTGTC_1 ... TTTGTCAAGCTGAACG_2 TTTGTCAGTATTAGCC_2
colData names(1): annotation
reducedDimNames(0):
spikeNames(0):
class: SingleCellExperiment 
dim: 31122 5793 
metadata(0):
assays(3): counts cpm logcounts
rownames(31122): A1BG A1BG-AS1 ... ZYX ZZEF1
rowData names(0):
colnames(5793): AAACGAAAGTGAACCG-1 AAACGAACATCGGCCA-1 ... TTTGTGTTCGATCGCG-1 TTTGTGTTCTGAGTAC-1
colData names(28): orig.ident nCount_ATAC ... nFeature_ACTIVITY ident
reducedDimNames(2): LSI UMAP
spikeNames(0):

Integration of T cells clusters

I re-run the integration based on the T cell subset. To select cells from the scATAC dataset, I take the SnapATAC clusters that best correspond to T-cells, based on label transfer.

tcells.sce.atac <- atac.sce[,which(as.numeric(atac.sce$seurat_clusters) %in% c(1:9))]

tcells.rna.ix <- which(rna.sce$annotation %in% c("DN","DP (Q)", "DP (P)", "SP (1)", "SP (2)"))
tcells.sce.rna <- rna.sce[,tcells.rna.ix]

tcells.sce.list <- list(RNA=tcells.sce.rna, ATAC=tcells.sce.atac)

## Make color palette 4 cell types
cell.types <- as.character(unique(tcells.sce.rna$annotation))
cell.type.pal <- brewer.pal(length(cell.types), "Set1") %>% rev() %>% setNames(cell.types)

Next, I select genes on which to perform integration. I take the union of the most variable features in the RNA dataset and the most covered features in the ATAC dataset

hcg.atac <- select_highlyCovered(tcells.sce.list$ATAC, frac_cells = 0.2)
hvg.rna <- select_highlyVariable(tcells.sce.list$RNA)
Calculating gene means
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Calculating gene variance to mean ratios
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
seu.rna <- FindVariableFeatures(seu.rna, nfeatures = 2000,
                                # selection.method = "mvp", dispersion.cutoff=c(0.7, 100), mean.cutoff=c(0.02, 3)
                                )
Calculating gene variances
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Calculating feature variances of standardized and clipped values
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
hvg.rna <- VariableFeatures(seu.rna)

VariableFeaturePlot(seu.rna)

UpSetR::upset(UpSetR::fromList(list(HVG.RNA=hvg.rna, HCG.ATAC=hcg.atac)))

Remove cell cycle genes, that might interfere with pseudotime ordering

cell_cycle_genes <- read.table("~/annotations/cell_cycle_genes.tsv")$V1

integrate_features_union <- union(hvg.rna, hcg.atac)
integrate_features_union <- setdiff(integrate_features_union, cell_cycle_genes) 

## Select features in both datasets
integrate_features_union <- intersect(integrate_features_union, intersect(rownames(tcells.sce.list$ATAC), rownames(tcells.sce.list$RNA))) 

Visualize T cells in RNA dataset

tcells.seu.list <- map(tcells.sce.list, ~ as.Seurat(.x))
All keys should be one or more alphanumeric characters followed by an underscore '_', setting key to LSI_All keys should be one or more alphanumeric characters followed by an underscore '_', setting key to UMAP_
tcells.RNA.union <- tcells.seu.list$RNA
VariableFeatures(tcells.RNA.union) <- integrate_features_union
tcells.RNA.union <- ScaleData(tcells.RNA.union) %>% RunPCA() %>% RunUMAP(dims=1:40)
Centering and scaling data matrix

  |                                                                                                                      
  |                                                                                                                |   0%
  |                                                                                                                      
  |======================                                                                                          |  20%
  |                                                                                                                      
  |=============================================                                                                   |  40%
  |                                                                                                                      
  |===================================================================                                             |  60%
  |                                                                                                                      
  |==========================================================================================                      |  80%
  |                                                                                                                      
  |================================================================================================================| 100%
The following 176 features requested have zero variance (running reduction without them): TM4SF18, MEOX2, ASGR2, DMRT2, KIR2DL4, CACNA1B, FREM2, MYZAP, TOX3, PRIMA1, PPP2R2B, PTPRZ1, CMTM5, TREM2, CALB2, TMTC1, CDH6, PKHD1L1, ISLR2, KRT7, MYH8, DCX, CCSER1, MSLN, CDH19, EYA4, P2RY12, PLA2G2D, ADAMTS16, KCNJ5, PAPPA, QRFPR, ADGRG2, NFIA-AS2, LUZP2, NALCN, FABP7, TSPEAR-AS1, GPR1, CD300LF, A4GALT, AP000439.1, CEACAM3, SLITRK2, KIR3DL1, AC147651.1, MUC15, FFAR4, C11orf53, KLK11, WIF1, CFHR1, TMEM233, FOLR3, GRID2, GALNT15, FAM19A1, TAC1, PTCHD4, CCL11, ZNF385B, GABRA1, ADH1B, LINC01048, LMOD1, TNNT1, ACTL6B, SEMA3E, HOXD-AS2, NTF4, SLC35F1, MANEAL, FEV, RAB3C, SYT9, HMCN2, ZFHX4-AS1, PCSK9, MMP12, ABCB11, AC002066.1, ADAM23, ADCY5, ADGB, ADGRB1, ANKRD29, ATP2B2, C4orf45, CCDC33, CDH18, CDK15, CERS1, CFAP161, CLRN1-AS1, CNBD1, CNGB3, CNTN5, COL8A2, CTNND2, DCC, DSCAM, EPHA6, FGF12, FSTL5, GABBR2, GABRB1, GABRG3, GALNT13, GHR, GLIS3, GNG12-AS1, GNGT1, GRIN2B, GRM1, GRM8, HMCN1, HPSE2, HS3ST5, HS6ST3, IL1RAPL2, ITGBL1, KCNB1, KCNH7, LINC00639, LINC01169, LINC01170, LINC01182, LINC01317, LINC01505, LINGO1, LINGO2, LRRC4C, LRRTM4, LY75-CD302, MAP6, MLIP, MYRIP, NEBL, NKAIN2, NKAIN3, NRG1, OPCML, PCDH15, PCDHGA4, PDZRN4, PIK3C2G, PKNOX2, PNPLA1, POU6F2, PPFIA2, PRR5-ARHGAP8, PTPN5, PTPRT, RALYL, RERG, RGS7, RIMS1, RIN2, RYR3, SHANK2, SNTG1, SORCS3, STON1-GTF2A1L, SYN2, TENM2, THSD4, TRPC6, TSPEAR, TVP23C-CDRT4, UNC80, USH1C, VAX2, VWA3B, XKR4, ZBTB7C, ZNF804BPC_ 1 
Positive:  SATB1, PTPRC, MTSS1, APBB1IP, LYST, SH2D1A, CAMK4, LBH, FBLN5, LEF1 
       PLEKHG1, VOPP1, CD247, MXD1, TCF12, ARHGEF7, ALDH1A2, NFATC3, TBC1D19, SYTL3 
       ADAMTS17, ANO6, DAPK1, CALN1, THEMIS, PITPNM2, NLGN4X, MAPRE2, GALNT7, ZNF280D 
Negative:  ENO1, GSTP1, FABP5, TMSB10, SMS, PKM, NME4, VIM, RPL37A, YWHAQ 
       NCL, IGFBP2, CAPG, NDUFA12, TRDC, PGK1, PARVB, LDHB, ATOX1, SELL 
       CDC123, NUDC, IGLL1, UBE2N, FXYD2, GMPS, C20orf27, SLC25A39, ANXA1, C12orf75 
PC_ 2 
Positive:  RPL37A, EIF3H, LDHB, TBCA, IL32, SERGEF, SMPD3, ITGAE, AATF, FBLN5 
       NDUFA12, SOD1, DNAJC15, C12orf75, MRPL33, TMSB10, HNRNPC, CCDC57, SMCO4, GDI2 
       OLA1, ALDH1A2, COX7A2L, CST3, CYSTM1, ATOX1, GYPC, PDCD6, FABP5, RALY 
Negative:  MBNL1, ITPR2, PTPRC, MTRNR2L12, ADAM10, MSI2, CDK6, SELL, JCHAIN, RNF213 
       MME, TCF12, BPTF, BCL11B, NIPBL, MACF1, PIK3R1, HIVEP3, SOCS2, GALNT7 
       IKZF2, RUNX1, SPTBN1, PRRC2B, BCL2, DIAPH1, MYCBP2, SLC38A1, MBP, RUFY3 
PC_ 3 
Positive:  HLA-B, TOX2, COTL1, CTSW, KLRB1, CD40LG, SIRPG, GZMM, CLDN1, CD74 
       ITM2A, GBP2, BACH2, HPGD, PDE4D, TNFRSF1B, S100A10, CLEC2D, XCL1, GFOD1 
       CRTAM, MATK, DENND2D, PDCD1, GIMAP4, STAT1, ZNF683, CD226, HLA-A, TNFRSF25 
Negative:  TFDP2, JCHAIN, ATP6AP1L, DEFA6, MME, GALNT2, PCGF5, ADGRG1, GLIPR1, MSI2 
       NINL, CEP70, CDK6, PTPN2, FXYD2, TRDC, GSTP1, SELL, SOCS2, RGPD3 
       SMPD3, FABP5, LYST, SSBP2, PITPNM2, DLEU7, UBE2E1, NUCB2, PPP1R1C, BCL11A 
PC_ 4 
Positive:  SMPD3, C12orf75, TUBA1C, LCP1, HIST1H2AB, SMS, SMCO4, GMPS, FABP5, XPO1 
       IGFBP2, CCND3, TAF15, RGS3, SREBF2, YWHAQ, TMSB15A, ABCD3, NCL, PHIP 
       GNAS, EPB41L2, SYNE2, STAG2, CNTLN, VIM, NUP210, MCTP1, NUDC, NFATC3 
Negative:  JCHAIN, SELL, DEFA6, SOCS2, GLIPR1, ATP6AP1L, MME, RGPD3, XG, DPP4 
       IFI6, ENAM, GNAL, EVL, NEGR1, FRMPD2, EVA1A, PIK3CD, GALNT2, NDST3 
       MBP, RNF144A, FXYD2, COL1A2, SMIM24, NDFIP2, OXNAD1, ACTN1, LSP1, LINC00861 
PC_ 5 
Positive:  HPGD, BACH2, TOX2, ITM2A, GZMM, ST6GAL1, ARAP2, CD96, TGFBR2, LZTFL1 
       VIM, EVL, CD44, SATB1, IL2, TUSC3, CYTIP, PDE4D, MAD1L1, SLC2A3 
       ARHGAP31, GPR183, PGK1, ANKRD44, STK4, BCL2, GNG2, IKZF1, ICOS, ETS1 
Negative:  XCL1, TNFRSF9, CTSW, XCL2, GBP2, TNFRSF1B, S100A4, GNG4, NPW, CD74 
       HOPX, IGFBP4, CD151, SH3BGRL2, NFATC1, LYST, ATP9A, NR4A2, LINC01480, CLEC2D 
       MIR3142HG, LINC01281, PREX1, HDAC9, PHACTR1, MAP7, CST3, CXCR3, PITPNM2, PDCD1 
The default method for RunUMAP has changed from calling Python UMAP via reticulate to the R-native UWOT using the cosine metric
To use Python UMAP via reticulate, set umap.method to 'umap-learn' and metric to 'correlation'
This message will be shown once per session09:11:47 UMAP embedding parameters a = 0.9922 b = 1.112
09:11:47 Read 7101 rows and found 40 numeric columns
09:11:47 Using Annoy for neighbor search, n_neighbors = 30
09:11:47 Building Annoy index with metric = cosine, n_trees = 50
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
09:11:48 Writing NN index file to temp file /tmp/RtmpIaaTXb/file9c876abf67
09:11:48 Searching Annoy index using 1 thread, search_k = 3000
09:11:51 Annoy recall = 100%
09:11:52 Commencing smooth kNN distance calibration using 1 thread
09:11:55 Initializing from normalized Laplacian + noise
09:11:55 Commencing optimization for 500 epochs, with 314138 positive edges
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
09:12:14 Optimization finished
DimPlot(tcells.RNA.union, group.by = "annotation", label=TRUE) + ggtitle("RNA - feature union")
Using `as.character()` on a quosure is deprecated as of rlang 0.3.0.
Please use `as_label()` or `as_name()` instead.
This warning is displayed once per session.

Visualize markers

t.cell.markers <- list(known.markers = c("CD34", "IGLL1", "TRGC2", "TRDC", "PTCRA", "TRBC2", "TRAC", "CD4", "CD8A", "CD8B"),
                       chemokine.receptors = c("CCR9", "CCR7"),
                       tcr.activation = c("CD5", "CD27"),
                       proliferation=c("PCNA", "CDK1", "MKI67"),
                       cyclin.D = c("CCND2", "CCND3"),
                       recombination=c("RAG1", "RAG2"),
                       apoptosis=c("HRK","BMF", "TP53INP1"),
                       stage.markers = c("ST18", "HIVEP3", "RGPD3", "SMPD3", "AQP3", "RORC", "SATB1", "TOX2")
                       ) 
# FeaturePlot(tcells.RNA.ref, features = t.cell.markers$known.markers, cols = viridis::viridis(n=10))
FeaturePlot(tcells.RNA.union, features = t.cell.markers$known.markers, cols = viridis::viridis(n=10))

Visualize T cells in ATAC dataset

Colored by clusters called with SnapATAC

tcells.ATAC.union <- tcells.seu.list$ATAC
# tcells.ATAC.union <- NormalizeData(tcells.ATAC.union)
VariableFeatures(tcells.ATAC.union) <- integrate_features_union
tcells.ATAC.union <- RunLSI(tcells.ATAC.union, n=50, scale.max = NULL)
RunLSI is being moved to Signac. Equivalent functionality can be achieved via the Signac::RunTFIDF and Signac::RunSVD functions; for more information on Signac, please see https://github.com/timoast/SignacRunLSI is being moved to Signac. Equivalent functionality can be achieved via the Signac::RunTFIDF and Signac::RunSVD functions; for more information on Signac, please see https://github.com/timoast/SignacRunLSI is being moved to Signac. Equivalent functionality can be achieved via the Signac::RunTFIDF and Signac::RunSVD functions; for more information on Signac, please see https://github.com/timoast/SignacPerforming TF-IDF normalization
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Running SVD on TF-IDF matrix
Scaling cell embeddings
Cannot add objects with duplicate keys (offending key: LSI_), setting key to 'lsi_'
tcells.ATAC.union <- RunUMAP(tcells.ATAC.union, reduction = "lsi", dims = 1:50)
10:10:05 UMAP embedding parameters a = 0.9922 b = 1.112
10:10:05 Read 4977 rows and found 50 numeric columns
10:10:05 Using Annoy for neighbor search, n_neighbors = 30
10:10:05 Building Annoy index with metric = cosine, n_trees = 50
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
10:10:06 Writing NN index file to temp file /tmp/RtmpIaaTXb/file9c861ce1d48
10:10:06 Searching Annoy index using 1 thread, search_k = 3000
10:10:08 Annoy recall = 100%
10:10:11 Commencing smooth kNN distance calibration using 1 thread
10:10:14 Initializing from normalized Laplacian + noise
10:10:14 Commencing optimization for 500 epochs, with 172060 positive edges
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
10:10:26 Optimization finished
Cannot add objects with duplicate keys (offending key: UMAP_), setting key to 'umap_'
DimPlot(tcells.ATAC.union, reduction = "umap", group.by = "seurat_clusters", label = TRUE) + ggtitle("ATAC gmat")

Run CCA

Makes imputed transcriptome profile for the ATAC-seq cells to allow co-embedding

sce.list <- tcells.sce.list
reference = "RNA"
query = "ATAC" 
seurat.list <- imap(sce.list, ~ as.Seurat(.x, assay=.y))
seurat.list <- imap(seurat.list, ~ RenameCells(.x, add.cell.id=.y))
## Scale data
seurat.list <- map(seurat.list, ~ ScaleData(.x))
## Calculate CCA anchors
transfer.anchors <- FindTransferAnchors(reference = seurat.list[[reference]], 
                                        query = seurat.list[[query]],
                                        features = integrate_features_union, 
                                        reduction = "cca")

## Impute expression profiles for ATAC cells (for all genes, not just integration features)
refdata <- GetAssayData(seurat.list$RNA, assay = "RNA", slot = "data")
imputation <- TransferData(anchorset = transfer.anchors, refdata = refdata, weight.reduction = seurat.list$ATAC[["LSI"]])

## Merge datasets and co-embed
seurat.list$ATAC[["RNA"]] <- imputation
coembed <- merge(x = seurat.list$RNA, y = seurat.list$ATAC)

coembed <- ScaleData(coembed, features = integrate_features_union, do.scale = FALSE)
coembed <- RunPCA(coembed, features = integrate_features_union, verbose = FALSE)
coembed <- RunUMAP(coembed, dims = 1:30)

coembed <- AddMetaData(coembed, metadata = ifelse(colnames(coembed) %in% colnames(seurat.list[[reference]]), reference, query), col.name = "tech")

Transfer labels on ATAC dataset

celltype.predictions <- TransferData(anchorset = transfer.anchors, 
                                     refdata = seurat.list[[reference]]$annotation, 
                                     weight.reduction = seurat.list$ATAC[["LSI"]])

coembed <- AddMetaData(coembed, metadata = celltype.predictions)
coembed@meta.data %<>%
  rownames_to_column() %>%
  dplyr::mutate(annotation=ifelse(is.na(predicted.id) , annotation, NA)) %>%
  column_to_rownames()

coembed@meta.data <-
  coembed@meta.data %>%
  rownames_to_column() %>%
  dplyr::mutate(annotation=ifelse(is.na(annotation) & prediction.score.max > 0.5, predicted.id, annotation)) %>%
  dplyr::mutate(annotation=ifelse(annotation=="SP (2)", NA, annotation)) %>%
  column_to_rownames()
CombinePlots(
  list(DimPlot(coembed, group.by = c("predicted.id"), cols = cell.type.pal) + ggtitle("prediction"),
  DimPlot(coembed, group.by = c("annotation"), cols = cell.type.pal) + ggtitle("Original + prediction")),
  legend = "top"
  )

FeaturePlot(coembed, features = "prediction.score.max", cells = which(coembed$tech=="ATAC")) + scale_color_viridis_c()
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.

Run Pseudotime analysis

Identify cell of origin among the DN cells based on expression of IGLL1 and CD34

FeaturePlot(coembed, features = c("IGLL1", "CD34"), split.by = "tech", slot = "data", cols = viridis::viridis(n=100))

cell.oo <-
  coembed@meta.data %>% 
  rownames_to_column("cell") %>%
  mutate(IGLL1=coembed@assays$RNA@counts["IGLL1",cell]) %>%
  select(cell, annotation, IGLL1) %>%
  arrange(-IGLL1) %>%
  filter(annotation=="DN") %>%
  top_n(1, IGLL1) %>%
  pull(cell)

coembed@reductions$umap@cell.embeddings %>%
  as.tibble(rownames="cell") %>%
  mutate(cell.oo = ifelse(cell %in% cell.oo, T, F)) %>%
  ggplot(aes(UMAP_1, UMAP_2)) +
  geom_point(color="grey50") +
  geom_point(data=. %>% filter(cell.oo),color='red') +
  ggrepel::geom_text_repel(data=. %>% filter(cell.oo), aes(label="cell of origin"), color='red') +
  theme_cowplot() 

coembed <- AddMetaData(coembed, ifelse(colnames(coembed)==cell.oo, TRUE, FALSE), col.name = "iroot_cell")
merged.sce <- SingleCellExperiment(list(counts=coembed@assays$RNA@counts, logcounts=coembed@assays$RNA@data), colData=coembed@meta.data[, c("annotation", "tech", "iroot_cell")],
                     reducedDims = map(coembed@reductions, ~ .x@cell.embeddings))

saveRDS(object = merged.sce, "~/my_data/Tcells_CCA_integration_20191203.RDS")
saveRDS(object = integrate_features_union, "~/my_data/intFeatures_Tcells_CCA_integration_20191203.RDS")

I infer pseudotime using the diffusion pseudotime algorithm as implemented in scanpy. Making an R/reticulate wrapper for this function would be nice, but for now, see multiOmic_benchmark/DPT_tcells.ipynb.

Read scanpy output and save in R object.

dpt <- read.csv('~/my_data/Tcells_CCA_integration_20191127_scanpy_dpt.csv') %>%
  select(X, dpt_pseudotime)

coembed <- AddMetaData(coembed, column_to_rownames(dpt, 'X'))
saveRDS(coembed, "~/my_data/Tcells_CCA_integration_seurat_20191203.Rmd")
coembed <- readRDS("~/my_data/Tcells_CCA_integration_seurat_20191203.Rmd")

Visualize pseudotime

FeaturePlot(coembed, reduction = "umap", feature = "dpt_pseudotime", split.by = "tech", col=viridis::viridis(10)) 

Save figure

coembed.umaps.pl <- plot_grid(
  DimPlot(coembed, group.by = c("tech")) + theme(legend.position = "top"),
  DimPlot(coembed, group.by = c("annotation"), cols = cell.type.pal, label = TRUE, label.size = 5) + theme(legend.position = "none"),
  FeaturePlot(coembed, reduction = "umap", feature = "dpt_pseudotime") + scale_color_viridis_c(name="Diffusion\npseudotime") + ggtitle(""),
  nrow=1, ncol=3, rel_widths = c(1,1,1.2),
  labels = c("A", "B", "C")
) 
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
coembed.umaps.pl +
  ggsave(paste0(outdir, "coembed_umaps.png"), width=12, height = 4)

coembed@meta.data %>%
  dplyr::mutate(`DPT rank`=dense_rank(dpt_pseudotime)) %>%
  ggplot(aes(`DPT rank`)) +
  geom_histogram(aes(fill=annotation), bins=50) +
  facet_grid(annotation~tech, scales="free_y") +
  theme_bw(base_size = 16) +
  scale_fill_manual(values = cell.type.pal)

Check expression of markers along pseudotime

coembed@assays$RNA@data[t.cell.markers$known.markers, ] %>%
  as.matrix() %>%
  reshape2::melt(varnames=c("gene", "cell")) %>%
  left_join(coembed@meta.data[,"dpt_pseudotime", drop=F] %>% rownames_to_column("cell")) %>%
  mutate(pseudotime.rank=dense_rank(dpt_pseudotime)) %>%
  group_by(gene) %>%
  arrange(pseudotime.rank) %>%
  # mutate(value=(value-min(value))/max(value)-min(value)) %>%
  mutate(value=zoo::rollmean(value, k=5, fill=NA)) %>% 
  # mutate(value=(value-mean(value))/sd(value)) %>%
  ungroup() %>%
  mutate(gene=factor(gene, levels=rev(unique(gene)))) %>%
  ggplot(aes(pseudotime.rank, gene, fill=value)) +
  geom_tile() +
  scale_fill_viridis_c(name="log expression") +
  theme_bw(base_size = 16) +
  theme(panel.border = element_blank(), panel.grid = element_blank())

Bin pseudotime and visualize cell type composition

dpt.df <- 
  coembed@meta.data %>%
  rownames_to_column("cell") %>%
  dplyr::mutate(dpt_rank=dense_rank(dpt_pseudotime)) %>%
  mutate(dpt_bin=cut(dpt_rank, breaks = 100)) %>%
  mutate(dpt_bin=as.numeric(dpt_bin)) %>%
  select(cell,tech, annotation, prediction.score.max, dpt_bin, dpt_pseudotime, dpt_rank)

cell.type.pl <- dpt.df %>%
  ggplot(aes(dpt_bin, fill = annotation)) +
  # geom_histogram(bins=100) +
  geom_bar() +
  scale_fill_manual(values=cell.type.pal, na.value="grey50") +
  facet_grid(tech~., scales="free_y") +
  xlab("Pseudotime bin") +
  theme_bw(base_size = 16)

cell.type.pl

Correlation between global accessibility and pseudotime ordering.

snap.out <- readRDS(file = "~/my_data/cellranger-atac110_count_30439_WSSS8038360_GRCh38-1_1_0.snapATAC.RDS")

groups <- dpt.df[dpt.df$tech=="ATAC", c("cell", "dpt_bin")]
bmat <- snap.out@bmat[str_remove(groups$cell, "ATAC_"),]
frac.accessible <- rowSums(bmat)/ncol(bmat)
acc.fraction.pl <- groups %>%
  mutate(frac_accessible=frac.accessible[str_remove(cell, "ATAC_")]) %>%
  ggplot(aes(dpt_bin, frac_accessible)) +
  geom_boxplot(aes(group=as.factor(dpt_bin)), outlier.alpha = 0.3, outlier.size = 0.7) +
  # geom_jitter(alpha=0.1) +
  xlab("Pseudotime bin") +
  ylab("Fraction of accessible bins") +
  facet_grid('ATAC'~.) +
  theme_bw(base_size = 16) 
  
dpt.pl <- plot_grid(cell.type.pl + theme(legend.position="top", axis.text.x = element_blank(), axis.ticks.x = element_blank(), axis.title.x = element_blank()), 
          acc.fraction.pl, 
          align = "v", ncol=1, nrow=2, axis="l")

dpt.pl +
  ggsave(paste0(outdir, "DPT_bins.png"), width=10, height = 7)

Motif analysis

I initially wanted to call peaks from SnapATAC clusters, then build a cell x peak matrix on those detected peaks, but SnapATAC/MACS2 don’t seem to work.

Alternative: load peak matrix from cellranger and add to snap object

filt.peaks <- Read10X_h5("~/my_data/filtered_peak_bc_matrix.h5")
peaks.mat <- str_split(rownames(filt.peaks), pattern = ":|-") %>% map(rbind) %>% purrr::reduce(rbind)
peaks.gr <- GRanges(peaks.mat[,1], IRanges(as.numeric(peaks.mat[,2]), as.numeric(peaks.mat[,3])))
snap.pmat <- createSnapFromPmat(mat=t(filt.peaks[,snap.out@barcode]), barcodes=snap.out@barcode, peaks=peaks.gr)
snap.pmat

Calculating deviations in TF accessibility using ChromVAR. This is a measure of how much is motif accessibility in each cell is enriched compared to all the cells and general cell coverage. While SnapATAC has an wrapper around ChromVAR that outputs the deviation matrix, I just take the code from that function and run every step separately to keep the useful outputs and statistics of chromVAR.

snap.pmat = makeBinary(snap.pmat, "pmat")

obj = snap.pmat
input.mat="pmat"
min.count=10
species="Homo sapiens"
genome=BSgenome.Hsapiens.UCSC.hg38

data.use = obj@pmat
peak.use = obj@peak

ncell = nrow(data.use)

idy = which(Matrix::colSums(data.use) >= min.count)
data.use = data.use[,idy,dropping=TRUE]
    
peak.use = peak.use[idy]

rse <- SummarizedExperiment(
        assays = list(counts = t(data.use)), 
                 rowRanges = peak.use, 
                 colData = DataFrame(Cell_Type=1:nrow(data.use), depth=Matrix::rowSums(data.use))
    );
rse <- addGCBias(rse, genome = genome);
motifs <- getJasparMotifs(collection = "CORE", species=species)
motif_mm <- matchMotifs(motifs, rse, genome = genome);
dev <- computeDeviations(object = rse, annotations = motif_mm);
var <- computeVariability(dev)

Save

rowData(dev) %<>%
  as.tibble(rownames="motif") %>%
  full_join(var) %>%
  column_to_rownames('motif') %>%
  DataFrame()

saveRDS(dev, "~/my_data/Tcells_peaks/Tcells_chromVarOutput.RDS")  

Visualize deviation scores of the most variable motifs, ordered in pseudotime.

png(paste0(outdir, "chromVAR_motif_heatmap.png"), width=1000, height = 900)
smooth.mmat %>%
  # mmat.topvar[,sample_dpt_bins.df$cell] %>%
  pheatmap::pheatmap(show_colnames = F, cluster_cols = F, clustering_distance_rows = "correlation",
                     annotation_col = sample_dpt_bins.df[,c("cell", "annotation", "dpt_pseudotime")] %>% column_to_rownames("cell"), 
                     annotation_colors = list(annotation=cell.type.pal, dpt_pseudotime=viridis::viridis(100)), fontsize = 18, fontsize_row = 12,
                     # color = colorRampPalette(rev(brewer.pal(n = 7, name ="Spectral")))(100))
                     breaks=seq(-3,3, length.out = 100)
  )
dev.off()
null device 
          1 
dpt.order <-
  dpt.df %>%
  filter(tech=="RNA") %>%
  arrange(dpt_pseudotime) 

coembed <- ScaleData(coembed, do.scale=TRUE)
Centering and scaling data matrix

  |                                                                                                                            
  |                                                                                                                      |   0%
  |                                                                                                                            
  |=====                                                                                                                 |   4%
  |                                                                                                                            
  |=========                                                                                                             |   8%
  |                                                                                                                            
  |==============                                                                                                        |  12%
  |                                                                                                                            
  |===================                                                                                                   |  16%
  |                                                                                                                            
  |========================                                                                                              |  20%
  |                                                                                                                            
  |============================                                                                                          |  24%
  |                                                                                                                            
  |=================================                                                                                     |  28%
  |                                                                                                                            
  |======================================                                                                                |  32%
  |                                                                                                                            
  |==========================================                                                                            |  36%
  |                                                                                                                            
  |===============================================                                                                       |  40%
  |                                                                                                                            
  |====================================================                                                                  |  44%
  |                                                                                                                            
  |=========================================================                                                             |  48%
  |                                                                                                                            
  |=============================================================                                                         |  52%
  |                                                                                                                            
  |==================================================================                                                    |  56%
  |                                                                                                                            
  |=======================================================================                                               |  60%
  |                                                                                                                            
  |============================================================================                                          |  64%
  |                                                                                                                            
  |================================================================================                                      |  68%
  |                                                                                                                            
  |=====================================================================================                                 |  72%
  |                                                                                                                            
  |==========================================================================================                            |  76%
  |                                                                                                                            
  |==============================================================================================                        |  80%
  |                                                                                                                            
  |===================================================================================================                   |  84%
  |                                                                                                                            
  |========================================================================================================              |  88%
  |                                                                                                                            
  |=============================================================================================================         |  92%
  |                                                                                                                            
  |=================================================================================================================     |  96%
  |                                                                                                                            
  |======================================================================================================================| 100%
gexmat.topvar <- coembed@assays$RNA@scale.data[tf.topvar[which(tf.topvar %in% rownames(coembed@assays$RNA@scale.data))],dpt.order$cell]
smooth.gexmat <- apply(gexmat.topvar, 1, function(x) zoo::rollmean(x, k=30)) %>% t() 
smooth.gexmat %>%
  # t() %>% scale() %>% t() %>%
  pheatmap::pheatmap(show_colnames = F, cluster_rows = T, cluster_cols = F, 
                     annotation_col = dpt.order[,c("cell", "annotation", "dpt_pseudotime")] %>% column_to_rownames("cell"),
                     annotation_colors = list(annotation=cell.type.pal, dpt_pseudotime=viridis::viridis(100)), fontsize = 18, fontsize_row = 12,
                     breaks=seq(-2,2, length.out = 100)
  )

Compare motif accessibility trend with gene expression trend along pseudotime. I find both examples of correlation between accessibility and TF expression (e.g. RUNX2, ELK3) and anti-correlation (e.g. JUN, ETV6).

map(list("JUN", "ELK3", "RUNX2", "REL", "FOS", "ETV6", "TCF3"), ~ plot.tfs(.x) + ggsave(paste0(outdir, paste0('TF_plot_',.x,".png")), width = 8, height=5))
Unequal factor levels: coercing to characterbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding factor and character vector, coercing into character vectorbinding character and factor vector, coercing into character vector
[[1]]

[[2]]

[[3]]

[[4]]

[[5]]

[[6]]

[[7]]

Ways to optimize this analysis

  • Improved motif database (more motifs, Jaspar2020 or HOCOMOCO)
  • Calling peaks on clusters ()

–>

–> –> –> –> –> –> –> –>

–> –> –> –> –> –> –>

–> –> –> –> –>

–> –> –> –>

–> –> –>


Ci0tLQp0aXRsZTogIlBzZXVkb3RpbWUgYW5hbHlzaXMgb2YgVC1jZWxscyBpbiBkZXZlbG9waW5nIHRoeW11cyIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKYGBge3J9CmxpYnJhcnkoU2V1cmF0KQpsaWJyYXJ5KGNvbm9zKQpsaWJyYXJ5KGdncHVicikKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoU2luZ2xlQ2VsbEV4cGVyaW1lbnQpCmxpYnJhcnkoY2hyb21WQVIpCmxpYnJhcnkobW90aWZtYXRjaHIpCmxpYnJhcnkoQlNnZW5vbWUuSHNhcGllbnMuVUNTQy5oZzM4KQojIGxpYnJhcnkobW9ub2NsZTMpCnNvdXJjZSgifi9tdWx0aU9taWNfYmVuY2htYXJrL3V0aWxzLlIiKQpzb3VyY2UoIn4vbXVsdGlPbWljX2JlbmNobWFyay9pbnRlZ3JhdGVCZW5jaG1hcmsuUiIpCnNvdXJjZSgifi9tdWx0aU9taWNfYmVuY2htYXJrL3ByZXByb2Nlc3Mvc2VsZWN0RmVhdHVyZXMuUiIpCgojIyBNYWtlIG91dHB1dCBkaXJlY3RvcnkKb3V0ZGlyIDwtICJ+L211bHRpT21pY19iZW5jaG1hcmsvcmVwb3J0L291dHB1dC8yMDE5MTEyN190Y2VsbFRyYWplY3RvcnkvIgppZmVsc2UoIWRpci5leGlzdHMob3V0ZGlyKSwgZGlyLmNyZWF0ZShvdXRkaXIpLCBGQUxTRSkKYGBgCiAKIApCYXNlZCBvbiB0aGUgcmVzdWx0cyBvZiBteSBiZW5jaG1hcmssIEkgc2V0IG91dCB0byBhbGlnbiBleHByZXNzaW9uIGFuZCBhY2Nlc3NpYmlsaXR5IHByb2ZpbGVzIGZyb20gdGhlIEY3NCBkZXZlbG9waW5nIHRoeW11cyBkYXRhc2V0IHRvIGRldGVjdCBjaGFuZ2VzIGluIGFjY2Vzc2liaWxpdHkgYWxvbmcgcHNldWRvdGltZSB0cmFqZWN0b3JpZXMuIFdoaWxlIHRoZSBiZW5jaG1hcmsgd2FzIGJhc2VkIG9uIHRoZSB0YXNrIG9mIGxhYmVsIHByb3BhZ2F0aW9uLCBJIGhlcmUgdXNlIHRoZSB0d28gbW9zdCBmYWl0aGZ1bCBtZXRob2RzIChTZXVyYXQgQ0NBIGFuZCBDb25vcykgdG8gYWNoaWV2ZSBhIGNvbW1vbiBlbWJlZGRpbmcgb2YgQVRBQy1zZXEgYW5kIFJOQS1zZXEgY2VsbHMuCgpMb2FkIGRhdGFzZXRzLgoKYGBge3J9CnJuYS5zY2UgPC0gcmVhZFJEUygifi9teV9kYXRhL0Y3NF9STkFfc2V1cmF0X3Byb2Nlc3NlZC5SRFMiKQphdGFjLnNjZSA8LSByZWFkUkRTKCJ+L215X2RhdGEvRjc0X0FUQUNfc25hcEF0YWNfcHJvY2Vzc2VkX2JnbWF0LlJEUyIpCgojIyBSZS1ub3JtYWxpemUgUk5BIGRhdGEKc2V1LnJuYSA8LSBhcy5TZXVyYXQocm5hLnNjZSwgY291bnRzID0gImNvdW50cyIpCnNldS5ybmEgPC0gTm9ybWFsaXplRGF0YShzZXUucm5hKQpsb2djb3VudHMocm5hLnNjZSkgPC0gc2V1LnJuYUBhc3NheXMkUk5BQGRhdGEKCmBgYAoKRmlsdGVyIGdlbmVzIHdpdGggemVybyB2YXJpYW5jZQpgYGB7cn0Kcm5hLmdlbmUudmFyIDwtIGFzLm1hdHJpeChjb3VudHMocm5hLnNjZSkpICU+JSByb3dWYXJzKCkKYXRhYy5nZW5lLnZhciA8LSBhcy5tYXRyaXgoY291bnRzKGF0YWMuc2NlKSkgJT4lIHJvd1ZhcnMoKQoKcm5hLnNjZSA8LSBybmEuc2NlW3doaWNoKHJuYS5nZW5lLnZhciA+IDApLF0KYXRhYy5zY2UgPC0gYXRhYy5zY2Vbd2hpY2goYXRhYy5nZW5lLnZhciA+IDApLF0KCnJuYS5zY2U7IGF0YWMuc2NlCmBgYAoKCiMjIEludGVncmF0aW9uIG9mIFQgY2VsbHMgY2x1c3RlcnMKSSByZS1ydW4gdGhlIGludGVncmF0aW9uIGJhc2VkIG9uIHRoZSBUIGNlbGwgc3Vic2V0LiBUbyBzZWxlY3QgY2VsbHMgZnJvbSB0aGUgc2NBVEFDIGRhdGFzZXQsIEkgdGFrZSB0aGUgU25hcEFUQUMgY2x1c3RlcnMgdGhhdCBiZXN0IGNvcnJlc3BvbmQgdG8gVC1jZWxscywgYmFzZWQgb24gbGFiZWwgdHJhbnNmZXIuCgpgYGB7cn0KdGNlbGxzLnNjZS5hdGFjIDwtIGF0YWMuc2NlWyx3aGljaChhcy5udW1lcmljKGF0YWMuc2NlJHNldXJhdF9jbHVzdGVycykgJWluJSBjKDE6OSkpXQoKdGNlbGxzLnJuYS5peCA8LSB3aGljaChybmEuc2NlJGFubm90YXRpb24gJWluJSBjKCJETiIsIkRQIChRKSIsICJEUCAoUCkiLCAiU1AgKDEpIiwgIlNQICgyKSIpKQp0Y2VsbHMuc2NlLnJuYSA8LSBybmEuc2NlWyx0Y2VsbHMucm5hLml4XQoKdGNlbGxzLnNjZS5saXN0IDwtIGxpc3QoUk5BPXRjZWxscy5zY2Uucm5hLCBBVEFDPXRjZWxscy5zY2UuYXRhYykKCiMjIE1ha2UgY29sb3IgcGFsZXR0ZSA0IGNlbGwgdHlwZXMKY2VsbC50eXBlcyA8LSBhcy5jaGFyYWN0ZXIodW5pcXVlKHRjZWxscy5zY2Uucm5hJGFubm90YXRpb24pKQpjZWxsLnR5cGUucGFsIDwtIGJyZXdlci5wYWwobGVuZ3RoKGNlbGwudHlwZXMpLCAiU2V0MSIpICU+JSByZXYoKSAlPiUgc2V0TmFtZXMoY2VsbC50eXBlcykKYGBgCgpOZXh0LCBJIHNlbGVjdCBnZW5lcyBvbiB3aGljaCB0byBwZXJmb3JtIGludGVncmF0aW9uLiBJIHRha2UgdGhlIHVuaW9uIG9mIHRoZSBtb3N0IHZhcmlhYmxlIGZlYXR1cmVzIGluIHRoZSBSTkEgZGF0YXNldCBhbmQgdGhlIG1vc3QgY292ZXJlZCBmZWF0dXJlcyBpbiB0aGUgQVRBQyBkYXRhc2V0CgpgYGB7cn0KaGNnLmF0YWMgPC0gc2VsZWN0X2hpZ2hseUNvdmVyZWQodGNlbGxzLnNjZS5saXN0JEFUQUMsIGZyYWNfY2VsbHMgPSAwLjIpCmh2Zy5ybmEgPC0gc2VsZWN0X2hpZ2hseVZhcmlhYmxlKHRjZWxscy5zY2UubGlzdCRSTkEpCgpzZXUucm5hIDwtIEZpbmRWYXJpYWJsZUZlYXR1cmVzKHNldS5ybmEsIG5mZWF0dXJlcyA9IDIwMDAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBzZWxlY3Rpb24ubWV0aG9kID0gIm12cCIsIGRpc3BlcnNpb24uY3V0b2ZmPWMoMC43LCAxMDApLCBtZWFuLmN1dG9mZj1jKDAuMDIsIDMpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKQpodmcucm5hIDwtIFZhcmlhYmxlRmVhdHVyZXMoc2V1LnJuYSkKClZhcmlhYmxlRmVhdHVyZVBsb3Qoc2V1LnJuYSkKVXBTZXRSOjp1cHNldChVcFNldFI6OmZyb21MaXN0KGxpc3QoSFZHLlJOQT1odmcucm5hLCBIQ0cuQVRBQz1oY2cuYXRhYykpKQpgYGAKClJlbW92ZSBjZWxsIGN5Y2xlIGdlbmVzLCB0aGF0IG1pZ2h0IGludGVyZmVyZSB3aXRoIHBzZXVkb3RpbWUgb3JkZXJpbmcKYGBge3J9CmNlbGxfY3ljbGVfZ2VuZXMgPC0gcmVhZC50YWJsZSgifi9hbm5vdGF0aW9ucy9jZWxsX2N5Y2xlX2dlbmVzLnRzdiIpJFYxCgppbnRlZ3JhdGVfZmVhdHVyZXNfdW5pb24gPC0gdW5pb24oaHZnLnJuYSwgaGNnLmF0YWMpCmludGVncmF0ZV9mZWF0dXJlc191bmlvbiA8LSBzZXRkaWZmKGludGVncmF0ZV9mZWF0dXJlc191bmlvbiwgY2VsbF9jeWNsZV9nZW5lcykgCgojIyBTZWxlY3QgZmVhdHVyZXMgaW4gYm90aCBkYXRhc2V0cwppbnRlZ3JhdGVfZmVhdHVyZXNfdW5pb24gPC0gaW50ZXJzZWN0KGludGVncmF0ZV9mZWF0dXJlc191bmlvbiwgaW50ZXJzZWN0KHJvd25hbWVzKHRjZWxscy5zY2UubGlzdCRBVEFDKSwgcm93bmFtZXModGNlbGxzLnNjZS5saXN0JFJOQSkpKSAKCmBgYAoKIyMjIyBWaXN1YWxpemUgVCBjZWxscyBpbiBSTkEgZGF0YXNldApgYGB7cn0KdGNlbGxzLnNldS5saXN0IDwtIG1hcCh0Y2VsbHMuc2NlLmxpc3QsIH4gYXMuU2V1cmF0KC54KSkKdGNlbGxzLlJOQS51bmlvbiA8LSB0Y2VsbHMuc2V1Lmxpc3QkUk5BClZhcmlhYmxlRmVhdHVyZXModGNlbGxzLlJOQS51bmlvbikgPC0gaW50ZWdyYXRlX2ZlYXR1cmVzX3VuaW9uCnRjZWxscy5STkEudW5pb24gPC0gU2NhbGVEYXRhKHRjZWxscy5STkEudW5pb24pICU+JSBSdW5QQ0EoKSAlPiUgUnVuVU1BUChkaW1zPTE6NDApCgpEaW1QbG90KHRjZWxscy5STkEudW5pb24sIGdyb3VwLmJ5ID0gImFubm90YXRpb24iLCBsYWJlbD1UUlVFKSArIGdndGl0bGUoIlJOQSAtIGZlYXR1cmUgdW5pb24iKQpgYGAKClZpc3VhbGl6ZSBtYXJrZXJzIApgYGB7ciwgZmlnLndpZHRoPTE1LCBmaWcuaGVpZ2h0PTEwfQp0LmNlbGwubWFya2VycyA8LSBsaXN0KGtub3duLm1hcmtlcnMgPSBjKCJDRDM0IiwgIklHTEwxIiwgIlRSR0MyIiwgIlRSREMiLCAiUFRDUkEiLCAiVFJCQzIiLCAiVFJBQyIsICJDRDQiLCAiQ0Q4QSIsICJDRDhCIiksCiAgICAgICAgICAgICAgICAgICAgICAgY2hlbW9raW5lLnJlY2VwdG9ycyA9IGMoIkNDUjkiLCAiQ0NSNyIpLAogICAgICAgICAgICAgICAgICAgICAgIHRjci5hY3RpdmF0aW9uID0gYygiQ0Q1IiwgIkNEMjciKSwKICAgICAgICAgICAgICAgICAgICAgICBwcm9saWZlcmF0aW9uPWMoIlBDTkEiLCAiQ0RLMSIsICJNS0k2NyIpLAogICAgICAgICAgICAgICAgICAgICAgIGN5Y2xpbi5EID0gYygiQ0NORDIiLCAiQ0NORDMiKSwKICAgICAgICAgICAgICAgICAgICAgICByZWNvbWJpbmF0aW9uPWMoIlJBRzEiLCAiUkFHMiIpLAogICAgICAgICAgICAgICAgICAgICAgIGFwb3B0b3Npcz1jKCJIUksiLCJCTUYiLCAiVFA1M0lOUDEiKSwKICAgICAgICAgICAgICAgICAgICAgICBzdGFnZS5tYXJrZXJzID0gYygiU1QxOCIsICJISVZFUDMiLCAiUkdQRDMiLCAiU01QRDMiLCAiQVFQMyIsICJST1JDIiwgIlNBVEIxIiwgIlRPWDIiKQogICAgICAgICAgICAgICAgICAgICAgICkgCiMgRmVhdHVyZVBsb3QodGNlbGxzLlJOQS5yZWYsIGZlYXR1cmVzID0gdC5jZWxsLm1hcmtlcnMka25vd24ubWFya2VycywgY29scyA9IHZpcmlkaXM6OnZpcmlkaXMobj0xMCkpCkZlYXR1cmVQbG90KHRjZWxscy5STkEudW5pb24sIGZlYXR1cmVzID0gdC5jZWxsLm1hcmtlcnMka25vd24ubWFya2VycywgY29scyA9IHZpcmlkaXM6OnZpcmlkaXMobj0xMCkpCmBgYAoKIyMjIyBWaXN1YWxpemUgVCBjZWxscyBpbiBBVEFDIGRhdGFzZXQKCkNvbG9yZWQgYnkgY2x1c3RlcnMgY2FsbGVkIHdpdGggU25hcEFUQUMKCmBgYHtyfQp0Y2VsbHMuQVRBQy51bmlvbiA8LSB0Y2VsbHMuc2V1Lmxpc3QkQVRBQwojIHRjZWxscy5BVEFDLnVuaW9uIDwtIE5vcm1hbGl6ZURhdGEodGNlbGxzLkFUQUMudW5pb24pClZhcmlhYmxlRmVhdHVyZXModGNlbGxzLkFUQUMudW5pb24pIDwtIGludGVncmF0ZV9mZWF0dXJlc191bmlvbgp0Y2VsbHMuQVRBQy51bmlvbiA8LSBSdW5MU0kodGNlbGxzLkFUQUMudW5pb24sIG49NTAsIHNjYWxlLm1heCA9IE5VTEwpCnRjZWxscy5BVEFDLnVuaW9uIDwtIFJ1blVNQVAodGNlbGxzLkFUQUMudW5pb24sIHJlZHVjdGlvbiA9ICJsc2kiLCBkaW1zID0gMTo1MCkKCkRpbVBsb3QodGNlbGxzLkFUQUMudW5pb24sIHJlZHVjdGlvbiA9ICJ1bWFwIiwgZ3JvdXAuYnkgPSAic2V1cmF0X2NsdXN0ZXJzIiwgbGFiZWwgPSBUUlVFKSArIGdndGl0bGUoIkFUQUMgZ21hdCIpCmBgYAoKIyMjIyBSdW4gQ0NBIAoKTWFrZXMgaW1wdXRlZCB0cmFuc2NyaXB0b21lIHByb2ZpbGUgZm9yIHRoZSBBVEFDLXNlcSBjZWxscyB0byBhbGxvdyBjby1lbWJlZGRpbmcKCmBgYHtyLCBmaWcud2lkdGg9MTIsIGZpZy5oZWlnaHQ9NSwgZXZhbD1GQUxTRX0Kc2NlLmxpc3QgPC0gdGNlbGxzLnNjZS5saXN0CnJlZmVyZW5jZSA9ICJSTkEiCnF1ZXJ5ID0gIkFUQUMiIApzZXVyYXQubGlzdCA8LSBpbWFwKHNjZS5saXN0LCB+IGFzLlNldXJhdCgueCwgYXNzYXk9LnkpKQpzZXVyYXQubGlzdCA8LSBpbWFwKHNldXJhdC5saXN0LCB+IFJlbmFtZUNlbGxzKC54LCBhZGQuY2VsbC5pZD0ueSkpCiMjIFNjYWxlIGRhdGEKc2V1cmF0Lmxpc3QgPC0gbWFwKHNldXJhdC5saXN0LCB+IFNjYWxlRGF0YSgueCkpCiMjIENhbGN1bGF0ZSBDQ0EgYW5jaG9ycwp0cmFuc2Zlci5hbmNob3JzIDwtIEZpbmRUcmFuc2ZlckFuY2hvcnMocmVmZXJlbmNlID0gc2V1cmF0Lmxpc3RbW3JlZmVyZW5jZV1dLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHF1ZXJ5ID0gc2V1cmF0Lmxpc3RbW3F1ZXJ5XV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmZWF0dXJlcyA9IGludGVncmF0ZV9mZWF0dXJlc191bmlvbiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZWR1Y3Rpb24gPSAiY2NhIikKCiMjIEltcHV0ZSBleHByZXNzaW9uIHByb2ZpbGVzIGZvciBBVEFDIGNlbGxzIChmb3IgYWxsIGdlbmVzLCBub3QganVzdCBpbnRlZ3JhdGlvbiBmZWF0dXJlcykKcmVmZGF0YSA8LSBHZXRBc3NheURhdGEoc2V1cmF0Lmxpc3QkUk5BLCBhc3NheSA9ICJSTkEiLCBzbG90ID0gImRhdGEiKQppbXB1dGF0aW9uIDwtIFRyYW5zZmVyRGF0YShhbmNob3JzZXQgPSB0cmFuc2Zlci5hbmNob3JzLCByZWZkYXRhID0gcmVmZGF0YSwgd2VpZ2h0LnJlZHVjdGlvbiA9IHNldXJhdC5saXN0JEFUQUNbWyJMU0kiXV0pCgojIyBNZXJnZSBkYXRhc2V0cyBhbmQgY28tZW1iZWQKc2V1cmF0Lmxpc3QkQVRBQ1tbIlJOQSJdXSA8LSBpbXB1dGF0aW9uCmNvZW1iZWQgPC0gbWVyZ2UoeCA9IHNldXJhdC5saXN0JFJOQSwgeSA9IHNldXJhdC5saXN0JEFUQUMpCgpjb2VtYmVkIDwtIFNjYWxlRGF0YShjb2VtYmVkLCBmZWF0dXJlcyA9IGludGVncmF0ZV9mZWF0dXJlc191bmlvbiwgZG8uc2NhbGUgPSBGQUxTRSkKY29lbWJlZCA8LSBSdW5QQ0EoY29lbWJlZCwgZmVhdHVyZXMgPSBpbnRlZ3JhdGVfZmVhdHVyZXNfdW5pb24sIHZlcmJvc2UgPSBGQUxTRSkKY29lbWJlZCA8LSBSdW5VTUFQKGNvZW1iZWQsIGRpbXMgPSAxOjMwKQoKY29lbWJlZCA8LSBBZGRNZXRhRGF0YShjb2VtYmVkLCBtZXRhZGF0YSA9IGlmZWxzZShjb2xuYW1lcyhjb2VtYmVkKSAlaW4lIGNvbG5hbWVzKHNldXJhdC5saXN0W1tyZWZlcmVuY2VdXSksIHJlZmVyZW5jZSwgcXVlcnkpLCBjb2wubmFtZSA9ICJ0ZWNoIikKYGBgCgoKYGBge3IsIGVjaG89RkFMU0V9CiMjIExvYWQgb3V0cHV0IGZvciBxdWljayBrbml0dGluZwpjb2VtYmVkIDwtIHJlYWRSRFMoIn4vbXlfZGF0YS9UY2VsbHNfQ0NBX2ludGVncmF0aW9uX3NldXJhdF8yMDE5MTIwMy5SbWQiKQpgYGAKCjwhLS0gUnVuIENvbm9zIC0tPgo8IS0tIGBgYHtyfSAtLT4KPCEtLSBkYXRhLnByb2Nlc3NlZCA8LSBtYXAoc2NlLmxpc3QsIH4gYXMuU2V1cmF0KC54KSkgIC0tPgo8IS0tIFZhcmlhYmxlRmVhdHVyZXMoZGF0YS5wcm9jZXNzZWRbW3JlZmVyZW5jZV1dKSA8LSBpbnRlZ3JhdGVfZmVhdHVyZXNfdW5pb24gLS0+CjwhLS0gVmFyaWFibGVGZWF0dXJlcyhkYXRhLnByb2Nlc3NlZFtbcXVlcnldXSkgPC0gaW50ZWdyYXRlX2ZlYXR1cmVzX3VuaW9uIC0tPgo8IS0tIGRhdGEucHJvY2Vzc2VkIDwtIG1hcChkYXRhLnByb2Nlc3NlZCwgfiBTY2FsZURhdGEoLngpICU+JSBSdW5QQ0EoZGltcz0xOjMwKSkgLS0+CjwhLS0gbC5jb24gPC0gQ29ub3MkbmV3KGRhdGEucHJvY2Vzc2VkLG4uY29yZXM9MzApIC0tPgo8IS0tIGwuY29uJGJ1aWxkR3JhcGgoaz0xNSxrLnNlbGY9NSxrLnNlbGYud2VpZ2g9MC4wMSxuY29tcHM9MzAsbi5vZGdlbmVzPTVlMyxzcGFjZT0nUENBJykgIC0tPgoKPCEtLSBsLmNvbiRmaW5kQ29tbXVuaXRpZXMocmVzb2x1dGlvbj0xLjUpIC0tPgo8IS0tIGwuY29uJGVtYmVkR3JhcGgoYWxwaGE9MS8yKSAtLT4KCjwhLS0gY29ub3Mub3V0IDwtIGNvbm9zLm1vZGVsJG1vZGVsIC0tPgo8IS0tIGwuY29uJHBsb3RHcmFwaChjb2xvci5ieSA9ICJzYW1wbGUiKSAtLT4KCjwhLS0gZ2VuZVggPC0gc2V1cmF0Lmxpc3RbW3JlZmVyZW5jZV1dQGFzc2F5cyRSTkFAc2NhbGUuZGF0YVszLF0gLS0+CjwhLS0gZ2VuZVggPC0gc2V0TmFtZXMoYW5ub3RhdGlvblssMV0sIHJvd25hbWVzKGFubm90YXRpb24pKSAtLT4KPCEtLSBuZXcubGFiZWwucHJvYmFiaWxpdGllcyA8LSBsLmNvbiRwcm9wYWdhdGVMYWJlbHMobGFiZWxzID0gZ2VuZVgsIHZlcmJvc2UgPSBULCBmaXhlZC5pbml0aWFsLmxhYmVscz1UKSAtLT4KPCEtLSBoaXN0KG5ldy5sYWJlbC5wcm9iYWJpbGl0aWVzKSAtLT4KPCEtLSBsLmNvbiRjb3JyZWN0R2VuZXMoZ2VuZXMgPSBpbnRlZ3JhdGVfZmVhdHVyZXNfdW5pb24sIGNvdW50Lm1hdHJpeCA9IE1hdHJpeChzZXVyYXQubGlzdCRBVEFDQGFzc2F5cyRBVEFDQGRhdGEpKSAtLT4KCjwhLS0gYGBgIC0tPgoKIyMjIyBUcmFuc2ZlciBsYWJlbHMgb24gQVRBQyBkYXRhc2V0CmBgYHtyLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9NSwgZXZhbD1GQUxTRX0KY2VsbHR5cGUucHJlZGljdGlvbnMgPC0gVHJhbnNmZXJEYXRhKGFuY2hvcnNldCA9IHRyYW5zZmVyLmFuY2hvcnMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVmZGF0YSA9IHNldXJhdC5saXN0W1tyZWZlcmVuY2VdXSRhbm5vdGF0aW9uLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdlaWdodC5yZWR1Y3Rpb24gPSBzZXVyYXQubGlzdCRBVEFDW1siTFNJIl1dKQoKY29lbWJlZCA8LSBBZGRNZXRhRGF0YShjb2VtYmVkLCBtZXRhZGF0YSA9IGNlbGx0eXBlLnByZWRpY3Rpb25zKQpjb2VtYmVkQG1ldGEuZGF0YSAlPD4lCiAgcm93bmFtZXNfdG9fY29sdW1uKCkgJT4lCiAgZHBseXI6Om11dGF0ZShhbm5vdGF0aW9uPWlmZWxzZShpcy5uYShwcmVkaWN0ZWQuaWQpICwgYW5ub3RhdGlvbiwgTkEpKSAlPiUKICBjb2x1bW5fdG9fcm93bmFtZXMoKQoKY29lbWJlZEBtZXRhLmRhdGEgPC0KICBjb2VtYmVkQG1ldGEuZGF0YSAlPiUKICByb3duYW1lc190b19jb2x1bW4oKSAlPiUKICBkcGx5cjo6bXV0YXRlKGFubm90YXRpb249aWZlbHNlKGlzLm5hKGFubm90YXRpb24pICYgcHJlZGljdGlvbi5zY29yZS5tYXggPiAwLjUsIHByZWRpY3RlZC5pZCwgYW5ub3RhdGlvbikpICU+JQogIGRwbHlyOjptdXRhdGUoYW5ub3RhdGlvbj1pZmVsc2UoYW5ub3RhdGlvbj09IlNQICgyKSIsIE5BLCBhbm5vdGF0aW9uKSkgJT4lCiAgY29sdW1uX3RvX3Jvd25hbWVzKCkKYGBgCgpgYGB7cn0KQ29tYmluZVBsb3RzKAogIGxpc3QoRGltUGxvdChjb2VtYmVkLCBncm91cC5ieSA9IGMoInByZWRpY3RlZC5pZCIpLCBjb2xzID0gY2VsbC50eXBlLnBhbCkgKyBnZ3RpdGxlKCJwcmVkaWN0aW9uIiksCiAgRGltUGxvdChjb2VtYmVkLCBncm91cC5ieSA9IGMoImFubm90YXRpb24iKSwgY29scyA9IGNlbGwudHlwZS5wYWwpICsgZ2d0aXRsZSgiT3JpZ2luYWwgKyBwcmVkaWN0aW9uIikpLAogIGxlZ2VuZCA9ICJ0b3AiCiAgKQpgYGAKYGBge3J9CkZlYXR1cmVQbG90KGNvZW1iZWQsIGZlYXR1cmVzID0gInByZWRpY3Rpb24uc2NvcmUubWF4IiwgY2VsbHMgPSB3aGljaChjb2VtYmVkJHRlY2g9PSJBVEFDIikpICsgc2NhbGVfY29sb3JfdmlyaWRpc19jKCkKYGBgCgoKIyMjIFJ1biBQc2V1ZG90aW1lIGFuYWx5c2lzIAoKSWRlbnRpZnkgY2VsbCBvZiBvcmlnaW4gYW1vbmcgdGhlIEROIGNlbGxzIGJhc2VkIG9uIGV4cHJlc3Npb24gb2YgSUdMTDEgYW5kIENEMzQKCmBgYHtyLCBmaWcuaGVpZ2h0PTEwLCBmaWcud2lkdGg9MTB9CkZlYXR1cmVQbG90KGNvZW1iZWQsIGZlYXR1cmVzID0gYygiSUdMTDEiLCAiQ0QzNCIpLCBzcGxpdC5ieSA9ICJ0ZWNoIiwgc2xvdCA9ICJkYXRhIiwgY29scyA9IHZpcmlkaXM6OnZpcmlkaXMobj0xMDApKQpgYGAKYGBge3IsIGV2YWw9RkFMU0V9CmNlbGwub28gPC0KICBjb2VtYmVkQG1ldGEuZGF0YSAlPiUgCiAgcm93bmFtZXNfdG9fY29sdW1uKCJjZWxsIikgJT4lCiAgbXV0YXRlKElHTEwxPWNvZW1iZWRAYXNzYXlzJFJOQUBjb3VudHNbIklHTEwxIixjZWxsXSkgJT4lCiAgc2VsZWN0KGNlbGwsIGFubm90YXRpb24sIElHTEwxKSAlPiUKICBhcnJhbmdlKC1JR0xMMSkgJT4lCiAgZmlsdGVyKGFubm90YXRpb249PSJETiIpICU+JQogIHRvcF9uKDEsIElHTEwxKSAlPiUKICBwdWxsKGNlbGwpCgpjb2VtYmVkQHJlZHVjdGlvbnMkdW1hcEBjZWxsLmVtYmVkZGluZ3MgJT4lCiAgYXMudGliYmxlKHJvd25hbWVzPSJjZWxsIikgJT4lCiAgbXV0YXRlKGNlbGwub28gPSBpZmVsc2UoY2VsbCAlaW4lIGNlbGwub28sIFQsIEYpKSAlPiUKICBnZ3Bsb3QoYWVzKFVNQVBfMSwgVU1BUF8yKSkgKwogIGdlb21fcG9pbnQoY29sb3I9ImdyZXk1MCIpICsKICBnZW9tX3BvaW50KGRhdGE9LiAlPiUgZmlsdGVyKGNlbGwub28pLGNvbG9yPSdyZWQnKSArCiAgZ2dyZXBlbDo6Z2VvbV90ZXh0X3JlcGVsKGRhdGE9LiAlPiUgZmlsdGVyKGNlbGwub28pLCBhZXMobGFiZWw9ImNlbGwgb2Ygb3JpZ2luIiksIGNvbG9yPSdyZWQnKSArCiAgdGhlbWVfY293cGxvdCgpIAoKY29lbWJlZCA8LSBBZGRNZXRhRGF0YShjb2VtYmVkLCBpZmVsc2UoY29sbmFtZXMoY29lbWJlZCk9PWNlbGwub28sIFRSVUUsIEZBTFNFKSwgY29sLm5hbWUgPSAiaXJvb3RfY2VsbCIpCgogIApgYGAKCgpgYGB7ciwgZXZhbD1GQUxTRX0KbWVyZ2VkLnNjZSA8LSBTaW5nbGVDZWxsRXhwZXJpbWVudChsaXN0KGNvdW50cz1jb2VtYmVkQGFzc2F5cyRSTkFAY291bnRzLCBsb2djb3VudHM9Y29lbWJlZEBhc3NheXMkUk5BQGRhdGEpLCBjb2xEYXRhPWNvZW1iZWRAbWV0YS5kYXRhWywgYygiYW5ub3RhdGlvbiIsICJ0ZWNoIiwgImlyb290X2NlbGwiKV0sCiAgICAgICAgICAgICAgICAgICAgIHJlZHVjZWREaW1zID0gbWFwKGNvZW1iZWRAcmVkdWN0aW9ucywgfiAueEBjZWxsLmVtYmVkZGluZ3MpKQoKc2F2ZVJEUyhvYmplY3QgPSBtZXJnZWQuc2NlLCAifi9teV9kYXRhL1RjZWxsc19DQ0FfaW50ZWdyYXRpb25fMjAxOTEyMDMuUkRTIikKc2F2ZVJEUyhvYmplY3QgPSBpbnRlZ3JhdGVfZmVhdHVyZXNfdW5pb24sICJ+L215X2RhdGEvaW50RmVhdHVyZXNfVGNlbGxzX0NDQV9pbnRlZ3JhdGlvbl8yMDE5MTIwMy5SRFMiKQpgYGAKCkkgaW5mZXIgcHNldWRvdGltZSB1c2luZyB0aGUgZGlmZnVzaW9uIHBzZXVkb3RpbWUgYWxnb3JpdGhtIGFzIGltcGxlbWVudGVkIGluIHNjYW5weS4gTWFraW5nIGFuIFIvcmV0aWN1bGF0ZSB3cmFwcGVyIGZvciB0aGlzIGZ1bmN0aW9uIHdvdWxkIGJlIG5pY2UsIGJ1dCBmb3Igbm93LCBzZWUgYG11bHRpT21pY19iZW5jaG1hcmsvRFBUX3RjZWxscy5pcHluYmAuCgpSZWFkIHNjYW5weSBvdXRwdXQgYW5kIHNhdmUgaW4gUiBvYmplY3QuCmBgYHtyLCBldmFsPUZBTFNFfQpkcHQgPC0gcmVhZC5jc3YoJ34vbXlfZGF0YS9UY2VsbHNfQ0NBX2ludGVncmF0aW9uXzIwMTkxMTI3X3NjYW5weV9kcHQuY3N2JykgJT4lCiAgc2VsZWN0KFgsIGRwdF9wc2V1ZG90aW1lKQoKY29lbWJlZCA8LSBBZGRNZXRhRGF0YShjb2VtYmVkLCBjb2x1bW5fdG9fcm93bmFtZXMoZHB0LCAnWCcpKQpzYXZlUkRTKGNvZW1iZWQsICJ+L215X2RhdGEvVGNlbGxzX0NDQV9pbnRlZ3JhdGlvbl9zZXVyYXRfMjAxOTEyMDMuUm1kIikKY29lbWJlZCA8LSByZWFkUkRTKCJ+L215X2RhdGEvVGNlbGxzX0NDQV9pbnRlZ3JhdGlvbl9zZXVyYXRfMjAxOTEyMDMuUm1kIikKYGBgCgoKVmlzdWFsaXplIHBzZXVkb3RpbWUKCmBgYHtyLCBmaWcud2lkdGg9MTB9CkZlYXR1cmVQbG90KGNvZW1iZWQsIHJlZHVjdGlvbiA9ICJ1bWFwIiwgZmVhdHVyZSA9ICJkcHRfcHNldWRvdGltZSIsIHNwbGl0LmJ5ID0gInRlY2giLCBjb2w9dmlyaWRpczo6dmlyaWRpcygxMCkpIApgYGAKClNhdmUgZmlndXJlCmBgYHtyLCBmaWcud2lkdGg9MTB9CmNvZW1iZWQudW1hcHMucGwgPC0gcGxvdF9ncmlkKAogIERpbVBsb3QoY29lbWJlZCwgZ3JvdXAuYnkgPSBjKCJ0ZWNoIikpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInRvcCIpLAogIERpbVBsb3QoY29lbWJlZCwgZ3JvdXAuYnkgPSBjKCJhbm5vdGF0aW9uIiksIGNvbHMgPSBjZWxsLnR5cGUucGFsLCBsYWJlbCA9IFRSVUUsIGxhYmVsLnNpemUgPSA1KSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiksCiAgRmVhdHVyZVBsb3QoY29lbWJlZCwgcmVkdWN0aW9uID0gInVtYXAiLCBmZWF0dXJlID0gImRwdF9wc2V1ZG90aW1lIikgKyBzY2FsZV9jb2xvcl92aXJpZGlzX2MobmFtZT0iRGlmZnVzaW9uXG5wc2V1ZG90aW1lIikgKyBnZ3RpdGxlKCIiKSwKICBucm93PTEsIG5jb2w9MywgcmVsX3dpZHRocyA9IGMoMSwxLDEuMiksCiAgbGFiZWxzID0gYygiQSIsICJCIiwgIkMiKQopIAoKY29lbWJlZC51bWFwcy5wbCArCiAgZ2dzYXZlKHBhc3RlMChvdXRkaXIsICJjb2VtYmVkX3VtYXBzLnBuZyIpLCB3aWR0aD0xMiwgaGVpZ2h0ID0gNCkKYGBgCgoKCmBgYHtyLCBmaWcuaGVpZ2h0PTgsIGZpZy53aWR0aD0xMH0KY29lbWJlZEBtZXRhLmRhdGEgJT4lCiAgZHBseXI6Om11dGF0ZShgRFBUIHJhbmtgPWRlbnNlX3JhbmsoZHB0X3BzZXVkb3RpbWUpKSAlPiUKICBnZ3Bsb3QoYWVzKGBEUFQgcmFua2ApKSArCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKGZpbGw9YW5ub3RhdGlvbiksIGJpbnM9NTApICsKICBmYWNldF9ncmlkKGFubm90YXRpb25+dGVjaCwgc2NhbGVzPSJmcmVlX3kiKSArCiAgdGhlbWVfYncoYmFzZV9zaXplID0gMTYpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjZWxsLnR5cGUucGFsKQoKYGBgCgpDaGVjayBleHByZXNzaW9uIG9mIG1hcmtlcnMgYWxvbmcgcHNldWRvdGltZQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLndpZHRoPTE0LCBmaWcuaGVpZ2h0PTR9CmNvZW1iZWRAYXNzYXlzJFJOQUBkYXRhW3QuY2VsbC5tYXJrZXJzJGtub3duLm1hcmtlcnMsIF0gJT4lCiAgYXMubWF0cml4KCkgJT4lCiAgcmVzaGFwZTI6Om1lbHQodmFybmFtZXM9YygiZ2VuZSIsICJjZWxsIikpICU+JQogIGxlZnRfam9pbihjb2VtYmVkQG1ldGEuZGF0YVssImRwdF9wc2V1ZG90aW1lIiwgZHJvcD1GXSAlPiUgcm93bmFtZXNfdG9fY29sdW1uKCJjZWxsIikpICU+JQogIG11dGF0ZShwc2V1ZG90aW1lLnJhbms9ZGVuc2VfcmFuayhkcHRfcHNldWRvdGltZSkpICU+JQogIGdyb3VwX2J5KGdlbmUpICU+JQogIGFycmFuZ2UocHNldWRvdGltZS5yYW5rKSAlPiUKICAjIG11dGF0ZSh2YWx1ZT0odmFsdWUtbWluKHZhbHVlKSkvbWF4KHZhbHVlKS1taW4odmFsdWUpKSAlPiUKICBtdXRhdGUodmFsdWU9em9vOjpyb2xsbWVhbih2YWx1ZSwgaz01LCBmaWxsPU5BKSkgJT4lIAogICMgbXV0YXRlKHZhbHVlPSh2YWx1ZS1tZWFuKHZhbHVlKSkvc2QodmFsdWUpKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgbXV0YXRlKGdlbmU9ZmFjdG9yKGdlbmUsIGxldmVscz1yZXYodW5pcXVlKGdlbmUpKSkpICU+JQogIGdncGxvdChhZXMocHNldWRvdGltZS5yYW5rLCBnZW5lLCBmaWxsPXZhbHVlKSkgKwogIGdlb21fdGlsZSgpICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfYyhuYW1lPSJsb2cgZXhwcmVzc2lvbiIpICsKICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNikgKwogIHRoZW1lKHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwgcGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSkKYGBgCgpCaW4gcHNldWRvdGltZSBhbmQgdmlzdWFsaXplIGNlbGwgdHlwZSBjb21wb3NpdGlvbgoKYGBge3IsIGZpZy53aWR0aD0xNSwgZmlnLmhlaWdodD00fQpkcHQuZGYgPC0gCiAgY29lbWJlZEBtZXRhLmRhdGEgJT4lCiAgcm93bmFtZXNfdG9fY29sdW1uKCJjZWxsIikgJT4lCiAgZHBseXI6Om11dGF0ZShkcHRfcmFuaz1kZW5zZV9yYW5rKGRwdF9wc2V1ZG90aW1lKSkgJT4lCiAgbXV0YXRlKGRwdF9iaW49Y3V0KGRwdF9yYW5rLCBicmVha3MgPSAxMDApKSAlPiUKICBtdXRhdGUoZHB0X2Jpbj1hcy5udW1lcmljKGRwdF9iaW4pKSAlPiUKICBzZWxlY3QoY2VsbCx0ZWNoLCBhbm5vdGF0aW9uLCBwcmVkaWN0aW9uLnNjb3JlLm1heCwgZHB0X2JpbiwgZHB0X3BzZXVkb3RpbWUsIGRwdF9yYW5rKQoKY2VsbC50eXBlLnBsIDwtIGRwdC5kZiAlPiUKICBnZ3Bsb3QoYWVzKGRwdF9iaW4sIGZpbGwgPSBhbm5vdGF0aW9uKSkgKwogICMgZ2VvbV9oaXN0b2dyYW0oYmlucz0xMDApICsKICBnZW9tX2JhcigpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9Y2VsbC50eXBlLnBhbCwgbmEudmFsdWU9ImdyZXk1MCIpICsKICBmYWNldF9ncmlkKHRlY2h+Liwgc2NhbGVzPSJmcmVlX3kiKSArCiAgeGxhYigiUHNldWRvdGltZSBiaW4iKSArCiAgdGhlbWVfYncoYmFzZV9zaXplID0gMTYpCgpjZWxsLnR5cGUucGwKYGBgCgpDb3JyZWxhdGlvbiBiZXR3ZWVuIGdsb2JhbCBhY2Nlc3NpYmlsaXR5IGFuZCBwc2V1ZG90aW1lIG9yZGVyaW5nLgoKYGBge3IsIGZpZy53aWR0aD0xNSwgZmlnLmhlaWdodD04fQpzbmFwLm91dCA8LSByZWFkUkRTKGZpbGUgPSAifi9teV9kYXRhL2NlbGxyYW5nZXItYXRhYzExMF9jb3VudF8zMDQzOV9XU1NTODAzODM2MF9HUkNoMzgtMV8xXzAuc25hcEFUQUMuUkRTIikKCmdyb3VwcyA8LSBkcHQuZGZbZHB0LmRmJHRlY2g9PSJBVEFDIiwgYygiY2VsbCIsICJkcHRfYmluIildCmJtYXQgPC0gc25hcC5vdXRAYm1hdFtzdHJfcmVtb3ZlKGdyb3VwcyRjZWxsLCAiQVRBQ18iKSxdCmZyYWMuYWNjZXNzaWJsZSA8LSByb3dTdW1zKGJtYXQpL25jb2woYm1hdCkKYWNjLmZyYWN0aW9uLnBsIDwtIGdyb3VwcyAlPiUKICBtdXRhdGUoZnJhY19hY2Nlc3NpYmxlPWZyYWMuYWNjZXNzaWJsZVtzdHJfcmVtb3ZlKGNlbGwsICJBVEFDXyIpXSkgJT4lCiAgZ2dwbG90KGFlcyhkcHRfYmluLCBmcmFjX2FjY2Vzc2libGUpKSArCiAgZ2VvbV9ib3hwbG90KGFlcyhncm91cD1hcy5mYWN0b3IoZHB0X2JpbikpLCBvdXRsaWVyLmFscGhhID0gMC4zLCBvdXRsaWVyLnNpemUgPSAwLjcpICsKICAjIGdlb21faml0dGVyKGFscGhhPTAuMSkgKwogIHhsYWIoIlBzZXVkb3RpbWUgYmluIikgKwogIHlsYWIoIkZyYWN0aW9uIG9mIGFjY2Vzc2libGUgYmlucyIpICsKICBmYWNldF9ncmlkKCdBVEFDJ34uKSArCiAgdGhlbWVfYncoYmFzZV9zaXplID0gMTYpIAogIApkcHQucGwgPC0gcGxvdF9ncmlkKGNlbGwudHlwZS5wbCArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0idG9wIiwgYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksIGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSwgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpKSwgCiAgICAgICAgICBhY2MuZnJhY3Rpb24ucGwsIAogICAgICAgICAgYWxpZ24gPSAidiIsIG5jb2w9MSwgbnJvdz0yLCBheGlzPSJsIikKCmRwdC5wbCArCiAgZ2dzYXZlKHBhc3RlMChvdXRkaXIsICJEUFRfYmlucy5wbmciKSwgd2lkdGg9MTAsIGhlaWdodCA9IDcpCmBgYAoKYGBge3IsIGZpZy5oZWlnaHQ9MTB9CmNvZW1iZWQudW1hcHMucGwgPC0gcGxvdF9ncmlkKAogIERpbVBsb3QoY29lbWJlZCwgZ3JvdXAuYnkgPSBjKCJ0ZWNoIikpICsgdGhlbWVfY2xhc3NpYyhiYXNlX3NpemUgPSAxNikgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAidG9wIikgLAogIERpbVBsb3QoY29lbWJlZCwgZ3JvdXAuYnkgPSBjKCJhbm5vdGF0aW9uIiksIGNvbHMgPSBjZWxsLnR5cGUucGFsLCBsYWJlbCA9IFRSVUUsIGxhYmVsLnNpemUgPSA1KSArIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTYpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSwKICBGZWF0dXJlUGxvdChjb2VtYmVkLCByZWR1Y3Rpb24gPSAidW1hcCIsIGZlYXR1cmUgPSAiZHB0X3BzZXVkb3RpbWUiKSArCiAgICB0aGVtZV9jbGFzc2ljKGJhc2Vfc2l6ZSA9IDE2KSArCiAgICBzY2FsZV9jb2xvcl92aXJpZGlzX2MobmFtZT0iRGlmZnVzaW9uIHBzZXVkb3RpbWUiKSArIGdndGl0bGUoIiIpICsgCiAgICBndWlkZXMoY29sb3I9Z3VpZGVfY29sb3JiYXIoYmFyd2lkdGggPSAxMCx0aXRsZS5wb3NpdGlvbiA9ICJ0b3AiKSkgKwogICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInRvcCIsIGxlZ2VuZC5qdXN0aWZpY2F0aW9uID0gImNlbnRlciIpICwKICBucm93PTMsIG5jb2w9MSwgcmVsX2hlaWdodHMgPSBjKDEuMiwxLDEuNSkKKSAKCnBsb3RfZ3JpZChjb2VtYmVkLnVtYXBzLnBsLCBkcHQucGwsIHJlbF93aWR0aHM9YygxLDEuNykpICsKICBnZ3NhdmUocGFzdGUwKG91dGRpciwgInRjZWxsc19maWd1cmUucG5nIiksIHdpZHRoPTEwLCBoZWlnaHQgPSAxMCkKYGBgCgo8IS0tIFZpeiBtYXJrZXJzIC0tPgo8IS0tIGBgYHtyfSAtLT4KPCEtLSBhY2MubWF0IDwtIGNvZW1iZWRAYXNzYXlzJEFUQUNAZGF0YSAtLT4KPCEtLSBtYXJrZXJzLmFjYyA8LSBhY2MubWF0W2ludGVyc2VjdChjKHQuY2VsbC5tYXJrZXJzJGtub3duLm1hcmtlcnMsIHQuY2VsbC5tYXJrZXJzJGNoZW1va2luZS5yZWNlcHRvcnMsIHQuY2VsbC5tYXJrZXJzJHJlY29tYmluYXRpb24pLCByb3duYW1lcyhhY2MubWF0KSksLCBkcm9wPUZdIC0tPgoKPCEtLSBtYXJrZXJzLmRmIDwtIGRhdGEuZnJhbWUodChhcy5tYXRyaXgobWFya2Vycy5hY2NbLGRwdC5kZiRjZWxsW2RwdC5kZiR0ZWNoPT0iQVRBQyJdXSkpKSAlPiUgLS0+CjwhLS0gICByb3duYW1lc190b19jb2x1bW4oImNlbGwiKSAlPiUgLS0+CjwhLS0gICBwaXZvdF9sb25nZXIoY29scyA9IHJvd25hbWVzKG1hcmtlcnMuYWNjKSwgbmFtZXNfdG8gPSAibWFya2VyLmdlbmUiLCB2YWx1ZXNfdG8gPSAiYWNjZXNzaWJpbGl0eSIpIC0tPgoKPCEtLSBhbm5vdGF0aW9uLmhtIDwtIGF0YWMuZHB0LmRmICU+JSAtLT4KPCEtLSAgIGdyb3VwX2J5KGRwdF9iaW4sIGFubm90YXRpb24pICU+JSAtLT4KPCEtLSAgIHN1bW1hcmlzZShuPW4oKSkgJT4lIC0tPgo8IS0tICAgZ2dwbG90KGFlcyhkcHRfYmluLCBhbm5vdGF0aW9uKSkgKyAtLT4KPCEtLSAgIGdlb21fdGlsZShhZXMoYWxwaGE9biwgZmlsbD1hbm5vdGF0aW9uKSkgICsgLS0+CjwhLS0gICB0aGVtZV9jbGFzc2ljKGJhc2Vfc2l6ZSA9IDE2KSArIC0tPgo8IS0tICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWNlbGwudHlwZS5wYWwsIG5hLnZhbHVlPSJncmV5NTAiKSArIC0tPgo8IS0tICAgZ3VpZGVzKGZpbGw9J25vbmUnLCBhbHBoYT0nbm9uZScpICsgLS0+CjwhLS0gICB0aGVtZShheGlzLmxpbmUgPSBlbGVtZW50X2JsYW5rKCksIGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCksIGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLCBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCkpIC0tPgoKPCEtLSBtYXJrZXJzLmhtIDwtIGF0YWMuZHB0LmRmICU+JSAtLT4KPCEtLSAgIGZ1bGxfam9pbihtYXJrZXJzLmRmKSAlPiUgLS0+CjwhLS0gICBncm91cF9ieShkcHRfYmluLCBtYXJrZXIuZ2VuZSkgJT4lIC0tPgo8IS0tICAgc3VtbWFyaXNlKGZyYWNfYWNjZXNzaWJsZT1zdW0oYWNjZXNzaWJpbGl0eSkvbigpKSAlPiUgLS0+CjwhLS0gICB1bmdyb3VwKCkgJT4lIC0tPgo8IS0tICAgbXV0YXRlKG1hcmtlci5nZW5lPWZhY3RvcihtYXJrZXIuZ2VuZSwgbGV2ZWxzID0gYygiQ0QzNCIsICJJR0xMMSIsICJUUkdDMiIsICJUUkRDIiwgIlBUQ1JBIiwgIlRSQkMyIiwgIkNDUjkiLCJDQ1I3IiwgIlJBRzEiLCAiUkFHMiIsICJUUkFDIiwgIkNENCIsICJDRDhBIiwgIkNEOEIiKSkpICU+JSAtLT4KPCEtLSAgIG11dGF0ZShtYXJrZXIuZ2VuZT1mYWN0b3IobWFya2VyLmdlbmUsIGxldmVscyA9IHJldihsZXZlbHMobWFya2VyLmdlbmUpKSkpICU+JSAtLT4KPCEtLSAgIGdyb3VwX2J5KG1hcmtlci5nZW5lKSAlPiUgLS0+CjwhLS0gICBtdXRhdGUoZnJhY19hY2Nlc3NpYmxlPShmcmFjX2FjY2Vzc2libGUgLSBtaW4oZnJhY19hY2Nlc3NpYmxlKSkvbWF4KGZyYWNfYWNjZXNzaWJsZSkgLSBtaW4oZnJhY19hY2Nlc3NpYmxlKSkgJT4lIC0tPgo8IS0tICAgZ2dwbG90KGFlcyhkcHRfYmluLCBtYXJrZXIuZ2VuZSwgZmlsbD1mcmFjX2FjY2Vzc2libGUpKSArICAtLT4KPCEtLSAgIGdlb21fdGlsZSgpICsgLS0+CjwhLS0gICBzY2FsZV9maWxsX3ZpcmlkaXNfYyhuYW1lPSJGcmFjLmNlbGxzIikgKyAtLT4KPCEtLSAgIHhsYWIoIlBzZXVkb3RpbWUgYmluIikgKyAtLT4KPCEtLSAgIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTYpICsgLS0+CjwhLS0gICB0aGVtZShheGlzLmxpbmUgPSBlbGVtZW50X2JsYW5rKCksIGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCksIGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpKSAtLT4KCjwhLS0gbGVnIDwtIGdldF9sZWdlbmQobWFya2Vycy5obSkgLS0+CjwhLS0gZ3IxIDwtIHBsb3RfZ3JpZChhbm5vdGF0aW9uLmhtLCBtYXJrZXJzLmhtICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSwgbnJvdz0yLCByZWxfaGVpZ2h0cyA9IGMoMSwyKSwgYWxpZ24gPSAidiIpIC0tPgo8IS0tIGdyMiA8LSBwbG90X2dyaWQoZ2dwbG90KCkgKyAgdGhlbWVfdm9pZCgpLGxlZywgbnJvdz0yLCByZWxfaGVpZ2h0cyA9IGMoMSwyKSkgLS0+CjwhLS0gcGxvdF9ncmlkKGdyMSwgZ3IyLCByZWxfd2lkdGhzID0gYygzLDEpKSAtLT4KPCEtLSBgYGAgLS0+CgojIyBNb3RpZiBhbmFseXNpcyAKCkkgaW5pdGlhbGx5IHdhbnRlZCB0byBjYWxsIHBlYWtzIGZyb20gU25hcEFUQUMgY2x1c3RlcnMsIHRoZW4gYnVpbGQgYSBjZWxsIHggcGVhayBtYXRyaXggb24gdGhvc2UgZGV0ZWN0ZWQgcGVha3MsIGJ1dCBTbmFwQVRBQy9NQUNTMiBkb24ndCBzZWVtIHRvIHdvcmsuIAoKPCEtLSAtLS0tIC0tPgo8IS0tICoqVGhpcyBkb2Vzbid0IHNlZW0gdG8gd29yayoqIC0tPgo8IS0tIENhbGwgcGVha3MgIC0tPgo8IS0tIGBgYHtyfSAtLT4KPCEtLSAjIyBDYWxsIHBlYWtzIG9uIGNsdXN0ZXJzIC0tPgo8IS0tIGNsdXN0ZXJzLnNlbCA8LSB1bmlxdWUodGNlbGxzLnNjZS5hdGFjJHNldXJhdF9jbHVzdGVycykgLS0+CjwhLS0gcGVha3MubHMgPSBtY2xhcHBseShzZXEoY2x1c3RlcnMuc2VsKSwgZnVuY3Rpb24oaSl7IC0tPgo8IS0tICAgcHJpbnQocGFzdGUoImNsdXN0ZXIiLCBjbHVzdGVycy5zZWxbaV0pKSAtLT4KPCEtLSAgIHBlYWtzID0gcnVuTUFDUyggLS0+CjwhLS0gICAgICAgb2JqPXNuYXAub3V0W3doaWNoKHNuYXAub3V0QG1ldGFEYXRhJGJhcmNvZGUgJWluJSBjb2xuYW1lcyh0Y2VsbHMuc2NlLmF0YWMpW3RjZWxscy5zY2UuYXRhYyRzZXVyYXRfY2x1c3RlcnM9PWNsdXN0ZXJzLnNlbFtpXV0pLF0sICAtLT4KPCEtLSAgICAgICBvdXRwdXQucHJlZml4PXBhc3RlMCgiVGNlbGxzX0Y3NF9jbHVzdGVyIiwgY2x1c3RlcnMuc2VsW2ldKSwgLS0+CjwhLS0gICAgICAgcGF0aC50by5zbmFwdG9vbHM9Ii9vcHQvY29uZGEvYmluL3NuYXB0b29scyIsIC0tPgo8IS0tICAgICAgIHBhdGgudG8ubWFjcz0iL29wdC9jb25kYS9iaW4vbWFjczIiLCAtLT4KPCEtLSAgICAgICBnc2l6ZT0iaHMiLCAjIG1tLCBocywgZXRjIC0tPgo8IS0tICAgICAgIGJ1ZmZlci5zaXplPTUwMCwgIC0tPgo8IS0tICAgICAgIG51bS5jb3Jlcz0zLCAtLT4KPCEtLSAgICAgICBtYWNzLm9wdGlvbnM9Ii0tbm9tb2RlbCAtLXNoaWZ0IDEwMCAtLWV4dCAyMDAgLS1xdmFsIDVlLTIgLUIgLS1TUE1SIiwgLS0+CjwhLS0gICAgICAgdG1wLmZvbGRlcj10ZW1wZGlyKCkgLS0+CjwhLS0gICkgLS0+CjwhLS0gcGVha3MgLS0+CjwhLS0gfSwgbWMuY29yZXM9NSkgLS0+Cgo8IS0tIHBlYWtzLm5hbWVzID0gbGlzdC5maWxlcygifi9teV9kYXRhL1RjZWxsc19wZWFrcy8iLCBwYXR0ZXJuPSJuYXJyb3dQZWFrIiwgZnVsbC5uYW1lcyA9IFQpIC0tPgo8IS0tIHBlYWsuZ3IubHMgPSBsYXBwbHkocGVha3MubmFtZXMsIGZ1bmN0aW9uKHgpeyAtLT4KPCEtLSAgIHBlYWsuZGYgPSByZWFkLnRhYmxlKHgpIC0tPgo8IS0tICAgR1JhbmdlcyhzdHJfcmVtb3ZlX2FsbChwZWFrLmRmWywxXSwgImInfCciKSwgSVJhbmdlcyhwZWFrLmRmWywyXSwgcGVhay5kZlssM10pKSAtLT4KPCEtLSB9KSAtLT4KPCEtLSBwZWFrLmdyID0gcmVkdWNlKFJlZHVjZShjLCBwZWFrLmdyLmxzKSkgLS0+Cgo8IS0tICMjIE1ha2UgY2VsbCBieSBwZWFrIG1hdHJpeCAobm90IHJ1biBoZXJlKSAtLT4KPCEtLSBwZWFrcy5kZiA9IGFzLmRhdGEuZnJhbWUocGVhay5ncilbLDE6M107IC0tPgo8IS0tIHdyaXRlLnRhYmxlKHBlYWtzLmRmLGZpbGUgPSAifi9teV9kYXRhL1RjZWxsc19wZWFrcy9wZWFrcy5jb21iaW5lZC5iZWQiLGFwcGVuZD1GQUxTRSwgLS0+CjwhLS0gCQlxdW90ZT0gRkFMU0Usc2VwPSJcdCIsIGVvbCA9ICJcbiIsIG5hID0gIk5BIiwgZGVjID0gIi4iLCAgLS0+CjwhLS0gCQlyb3cubmFtZXMgPSBGQUxTRSwgY29sLm5hbWVzID0gRkFMU0UsIHFtZXRob2QgPSBjKCJlc2NhcGUiLCAiZG91YmxlIiksIC0tPgo8IS0tIAkJZmlsZUVuY29kaW5nID0gIiIpIC0tPgo8IS0tIGBgYCAtLT4KCjwhLS0gTWFraW5nIGNvbW1vbiBwZWFrIHJlZmVyZW5jZSB3aXRoIHNuYXB0b29scy4gSW4gdGVybWluYWwgLS0+CjwhLS0gYGBgIC0tPgo8IS0tIHNuYXB0b29scyBzbmFwLWFkZC1wbWF0IC0tc25hcC1maWxlIH4vbXlfZGF0YS9jZWxscmFuZ2VyLWF0YWMxMTBfY291bnRfMzA0MzlfV1NTUzgwMzgzNjBfR1JDaDM4LTFfMV8wLnNuYXAgLS1wZWFrLWZpbGUgcGVha3MuY29tYmluZWQuYmVkICAtLT4KPCEtLSBgYGAgLS0+Cgo8IS0tIEFkZCBwbWF0IHRvIHNuYXAgb2JqZWN0IC0tPgo8IS0tIGBgYHtyfSAtLT4KPCEtLSBzbmFwLm91dCA8LSBjcmVhdGVQbWF0KHNuYXAub3V0LCBwZWFrLmdyLCBkby5wYXIgPSBULCBudW0uY29yZXMgPSAxMCkgLS0+CjwhLS0gYGBgIC0tPgo8IS0tIC0tLSAtLT4KCkFsdGVybmF0aXZlOiBsb2FkIHBlYWsgbWF0cml4IGZyb20gY2VsbHJhbmdlciBhbmQgYWRkIHRvIHNuYXAgb2JqZWN0CmBgYHtyLCBldmFsPUZBTFNFfQpmaWx0LnBlYWtzIDwtIFJlYWQxMFhfaDUoIn4vbXlfZGF0YS9maWx0ZXJlZF9wZWFrX2JjX21hdHJpeC5oNSIpCnBlYWtzLm1hdCA8LSBzdHJfc3BsaXQocm93bmFtZXMoZmlsdC5wZWFrcyksIHBhdHRlcm4gPSAiOnwtIikgJT4lIG1hcChyYmluZCkgJT4lIHB1cnJyOjpyZWR1Y2UocmJpbmQpCnBlYWtzLmdyIDwtIEdSYW5nZXMocGVha3MubWF0WywxXSwgSVJhbmdlcyhhcy5udW1lcmljKHBlYWtzLm1hdFssMl0pLCBhcy5udW1lcmljKHBlYWtzLm1hdFssM10pKSkKc25hcC5wbWF0IDwtIGNyZWF0ZVNuYXBGcm9tUG1hdChtYXQ9dChmaWx0LnBlYWtzWyxzbmFwLm91dEBiYXJjb2RlXSksIGJhcmNvZGVzPXNuYXAub3V0QGJhcmNvZGUsIHBlYWtzPXBlYWtzLmdyKQpzbmFwLnBtYXQKYGBgCgpDYWxjdWxhdGluZyBkZXZpYXRpb25zIGluIFRGIGFjY2Vzc2liaWxpdHkgdXNpbmcgQ2hyb21WQVIuIFRoaXMgaXMgYSBtZWFzdXJlIG9mIGhvdyBtdWNoIGlzIG1vdGlmIGFjY2Vzc2liaWxpdHkgaW4gZWFjaCBjZWxsIGlzIGVucmljaGVkIGNvbXBhcmVkIHRvIGFsbCB0aGUgY2VsbHMgYW5kIGdlbmVyYWwgY2VsbCBjb3ZlcmFnZS4gV2hpbGUgU25hcEFUQUMgaGFzIGFuIHdyYXBwZXIgYXJvdW5kIENocm9tVkFSIHRoYXQgb3V0cHV0cyB0aGUgZGV2aWF0aW9uIG1hdHJpeCwgSSBqdXN0IHRha2UgdGhlIGNvZGUgZnJvbSB0aGF0IGZ1bmN0aW9uIGFuZCBydW4gZXZlcnkgc3RlcCBzZXBhcmF0ZWx5IHRvIGtlZXAgdGhlIHVzZWZ1bCBvdXRwdXRzIGFuZCBzdGF0aXN0aWNzIG9mIGNocm9tVkFSLgoKYGBge3IsIGV2YWw9RkFMU0V9CnNuYXAucG1hdCA9IG1ha2VCaW5hcnkoc25hcC5wbWF0LCAicG1hdCIpCgpvYmogPSBzbmFwLnBtYXQKaW5wdXQubWF0PSJwbWF0IgptaW4uY291bnQ9MTAKc3BlY2llcz0iSG9tbyBzYXBpZW5zIgpnZW5vbWU9QlNnZW5vbWUuSHNhcGllbnMuVUNTQy5oZzM4CgpkYXRhLnVzZSA9IG9iakBwbWF0CnBlYWsudXNlID0gb2JqQHBlYWsKCm5jZWxsID0gbnJvdyhkYXRhLnVzZSkKCmlkeSA9IHdoaWNoKE1hdHJpeDo6Y29sU3VtcyhkYXRhLnVzZSkgPj0gbWluLmNvdW50KQpkYXRhLnVzZSA9IGRhdGEudXNlWyxpZHksZHJvcHBpbmc9VFJVRV0KCQpwZWFrLnVzZSA9IHBlYWsudXNlW2lkeV0KCnJzZSA8LSBTdW1tYXJpemVkRXhwZXJpbWVudCgKCQlhc3NheXMgPSBsaXN0KGNvdW50cyA9IHQoZGF0YS51c2UpKSwgCgkJCQkgcm93UmFuZ2VzID0gcGVhay51c2UsIAoJCQkJIGNvbERhdGEgPSBEYXRhRnJhbWUoQ2VsbF9UeXBlPTE6bnJvdyhkYXRhLnVzZSksIGRlcHRoPU1hdHJpeDo6cm93U3VtcyhkYXRhLnVzZSkpCgkpOwpyc2UgPC0gYWRkR0NCaWFzKHJzZSwgZ2Vub21lID0gZ2Vub21lKTsKbW90aWZzIDwtIGdldEphc3Bhck1vdGlmcyhjb2xsZWN0aW9uID0gIkNPUkUiLCBzcGVjaWVzPXNwZWNpZXMpCm1vdGlmX21tIDwtIG1hdGNoTW90aWZzKG1vdGlmcywgcnNlLCBnZW5vbWUgPSBnZW5vbWUpOwpkZXYgPC0gY29tcHV0ZURldmlhdGlvbnMob2JqZWN0ID0gcnNlLCBhbm5vdGF0aW9ucyA9IG1vdGlmX21tKTsKdmFyIDwtIGNvbXB1dGVWYXJpYWJpbGl0eShkZXYpCmBgYAoKU2F2ZQpgYGB7ciwgZXZhbD1GQUxTRX0Kcm93RGF0YShkZXYpICU8PiUKICBhcy50aWJibGUocm93bmFtZXM9Im1vdGlmIikgJT4lCiAgZnVsbF9qb2luKHZhcikgJT4lCiAgY29sdW1uX3RvX3Jvd25hbWVzKCdtb3RpZicpICU+JQogIERhdGFGcmFtZSgpCgpzYXZlUkRTKGRldiwgIn4vbXlfZGF0YS9UY2VsbHNfcGVha3MvVGNlbGxzX2Nocm9tVmFyT3V0cHV0LlJEUyIpICAKYGBgCgpgYGB7ciwgZWNobz1GQUxTRX0KZGV2IDwtIHJlYWRSRFMoIn4vbXlfZGF0YS9UY2VsbHNfcGVha3MvVGNlbGxzX2Nocm9tVmFyT3V0cHV0LlJEUyIpICAKYGBgCgpgYGB7cn0KdmFyICU+JQogIG11dGF0ZShzaWduaWY9aWZlbHNlKHBfdmFsdWVfYWRqIDwgMC4wMSwgInNpZ25pZiIsIE5BKSkgJT4lCiAgbXV0YXRlKHJhbms9cmFuaygtdmFyaWFiaWxpdHkpKSAlPiUKICBnZ3Bsb3QoYWVzKHJhbmssIHZhcmlhYmlsaXR5LCBjb2xvcj1zaWduaWYpKSArCiAgZ2VvbV9wb2ludCgpICsKICBnZ3JlcGVsOjpnZW9tX3RleHRfcmVwZWwoZGF0YT0uICU+JSBmaWx0ZXIocmFuayA8IDgwICYgcmFuayA+IDUwKSwgYWVzKGxhYmVsPW5hbWUpKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gNTApCmBgYAoKClZpc3VhbGl6ZSBkZXZpYXRpb24gc2NvcmVzIG9mIHRoZSBtb3N0IHZhcmlhYmxlIG1vdGlmcywgb3JkZXJlZCBpbiBwc2V1ZG90aW1lLgoKYGBge3IsIGZpZy53aWR0aD0xNSwgZmlnLmhlaWdodD0xMH0Kc2FtcGxlX2RwdF9iaW5zLmRmIDwtIGRwdC5kZiAlPiUKICBtdXRhdGUoY2VsbD1zdHJfcmVtb3ZlKGNlbGwsICJeQVRBQ18iKSkgJT4lCiAgZmlsdGVyKHRlY2g9PSJBVEFDIikgJT4lCiAgYXJyYW5nZShkcHRfcHNldWRvdGltZSkKCm1vdGlmLnRvcHZhciA8LSB2YXIgJT4lIHJvd25hbWVzX3RvX2NvbHVtbigibW90aWYiKSAlPiUgdG9wX24oNTAsdmFyaWFiaWxpdHkpICU+JSBwdWxsKG1vdGlmKQp0Zi50b3B2YXIgPC0gbW90aWYudG9wdmFyICU+JSBzdHJfcmVtb3ZlKCIuK18iKSAlPiUgc3RyX3JlbW92ZSgiXFwoLit8Oi4rIikKbW1hdC50b3B2YXIgPC0gZGV2QGFzc2F5cyRkYXRhJHpbbW90aWYudG9wdmFyLHNhbXBsZV9kcHRfYmlucy5kZiRjZWxsXQoKcm93bmFtZXMobW1hdC50b3B2YXIpIDwtIHRmLnRvcHZhcgpzbW9vdGgubW1hdCA8LSBhcHBseShtbWF0LnRvcHZhciwgMSwgZnVuY3Rpb24oeCkgem9vOjpyb2xsbWVhbih4LCBrPTMwKSkgJT4lIHQoKSAKCnBuZyhwYXN0ZTAob3V0ZGlyLCAiY2hyb21WQVJfbW90aWZfaGVhdG1hcC5wbmciKSwgd2lkdGg9MTAwMCwgaGVpZ2h0ID0gOTAwKQpzbW9vdGgubW1hdCAlPiUKICAjIG1tYXQudG9wdmFyWyxzYW1wbGVfZHB0X2JpbnMuZGYkY2VsbF0gJT4lCiAgcGhlYXRtYXA6OnBoZWF0bWFwKHNob3dfY29sbmFtZXMgPSBGLCBjbHVzdGVyX2NvbHMgPSBGLCBjbHVzdGVyaW5nX2Rpc3RhbmNlX3Jvd3MgPSAiY29ycmVsYXRpb24iLAogICAgICAgICAgICAgICAgICAgICBhbm5vdGF0aW9uX2NvbCA9IHNhbXBsZV9kcHRfYmlucy5kZlssYygiY2VsbCIsICJhbm5vdGF0aW9uIiwgImRwdF9wc2V1ZG90aW1lIildICU+JSBjb2x1bW5fdG9fcm93bmFtZXMoImNlbGwiKSwgCiAgICAgICAgICAgICAgICAgICAgIGFubm90YXRpb25fY29sb3JzID0gbGlzdChhbm5vdGF0aW9uPWNlbGwudHlwZS5wYWwsIGRwdF9wc2V1ZG90aW1lPXZpcmlkaXM6OnZpcmlkaXMoMTAwKSksIGZvbnRzaXplID0gMTgsIGZvbnRzaXplX3JvdyA9IDEyLAogICAgICAgICAgICAgICAgICAgICAjIGNvbG9yID0gY29sb3JSYW1wUGFsZXR0ZShyZXYoYnJld2VyLnBhbChuID0gNywgbmFtZSA9IlNwZWN0cmFsIikpKSgxMDApKQogICAgICAgICAgICAgICAgICAgICBicmVha3M9c2VxKC0zLDMsIGxlbmd0aC5vdXQgPSAxMDApCiAgKQpkZXYub2ZmKCkKYGBgCgpgYGB7ciwgZmlnLndpZHRoPTEwfQpkcHQub3JkZXIgPC0KICBkcHQuZGYgJT4lCiAgZmlsdGVyKHRlY2g9PSJSTkEiKSAlPiUKICBhcnJhbmdlKGRwdF9wc2V1ZG90aW1lKSAKCmNvZW1iZWQgPC0gU2NhbGVEYXRhKGNvZW1iZWQsIGRvLnNjYWxlPVRSVUUpCmdleG1hdC50b3B2YXIgPC0gY29lbWJlZEBhc3NheXMkUk5BQHNjYWxlLmRhdGFbdGYudG9wdmFyW3doaWNoKHRmLnRvcHZhciAlaW4lIHJvd25hbWVzKGNvZW1iZWRAYXNzYXlzJFJOQUBzY2FsZS5kYXRhKSldLGRwdC5vcmRlciRjZWxsXQpzbW9vdGguZ2V4bWF0IDwtIGFwcGx5KGdleG1hdC50b3B2YXIsIDEsIGZ1bmN0aW9uKHgpIHpvbzo6cm9sbG1lYW4oeCwgaz0zMCkpICU+JSB0KCkgCnNtb290aC5nZXhtYXQgJT4lCiAgIyB0KCkgJT4lIHNjYWxlKCkgJT4lIHQoKSAlPiUKICBwaGVhdG1hcDo6cGhlYXRtYXAoc2hvd19jb2xuYW1lcyA9IEYsIGNsdXN0ZXJfcm93cyA9IFQsIGNsdXN0ZXJfY29scyA9IEYsIAogICAgICAgICAgICAgICAgICAgICBhbm5vdGF0aW9uX2NvbCA9IGRwdC5vcmRlclssYygiY2VsbCIsICJhbm5vdGF0aW9uIiwgImRwdF9wc2V1ZG90aW1lIildICU+JSBjb2x1bW5fdG9fcm93bmFtZXMoImNlbGwiKSwKICAgICAgICAgICAgICAgICAgICAgYW5ub3RhdGlvbl9jb2xvcnMgPSBsaXN0KGFubm90YXRpb249Y2VsbC50eXBlLnBhbCwgZHB0X3BzZXVkb3RpbWU9dmlyaWRpczo6dmlyaWRpcygxMDApKSwgZm9udHNpemUgPSAxOCwgZm9udHNpemVfcm93ID0gMTIsCiAgICAgICAgICAgICAgICAgICAgIGJyZWFrcz1zZXEoLTIsMiwgbGVuZ3RoLm91dCA9IDEwMCkKICApCmBgYAoKQ29tcGFyZSBtb3RpZiBhY2Nlc3NpYmlsaXR5IHRyZW5kIHdpdGggZ2VuZSBleHByZXNzaW9uIHRyZW5kIGFsb25nIHBzZXVkb3RpbWUuIEkgZmluZCBib3RoIGV4YW1wbGVzIG9mIGNvcnJlbGF0aW9uIGJldHdlZW4gYWNjZXNzaWJpbGl0eSBhbmQgVEYgZXhwcmVzc2lvbiAoZS5nLiBSVU5YMiwgRUxLMykgYW5kIGFudGktY29ycmVsYXRpb24gKGUuZy4gSlVOLCBFVFY2KS4KCmBgYHtyfQpjb3VudHMudG9wdmFyIDwtIGNvZW1iZWRAYXNzYXlzJFJOQUBkYXRhW3RmLnRvcHZhclt3aGljaCh0Zi50b3B2YXIgJWluJSByb3duYW1lcyhjb2VtYmVkQGFzc2F5cyRSTkFAZGF0YSkpXSxkcHQub3JkZXIkY2VsbF0KZ2V4LmRmIDwtIAogIHJlc2hhcGUyOjptZWx0KGFzLm1hdHJpeChjb3VudHMudG9wdmFyKSwgdmFybmFtZXM9YygiZ2VuZSIsICJjZWxsIikpICU+JSAKICAjIHJvd2lkX3RvX2NvbHVtbigiZHB0X29yZGVyIikgJT4lCiAgbXV0YXRlKGRhdGE9IkdlbmVcbmV4cHJlc3Npb24iKQptbWF0LmRmIDwtIHJlc2hhcGUyOjptZWx0KG1tYXQudG9wdmFyLCB2YXJuYW1lcz1jKCJnZW5lIiwgImNlbGwiKSkgJT4lCiAgIyByb3dpZF90b19jb2x1bW4oImRwdF9vcmRlciIpICU+JQogIG11dGF0ZShjZWxsPXN0cl9jKCJBVEFDXyIsIGNlbGwpKSAlPiUKICBtdXRhdGUoZGF0YT0iTW90aWZcbmRldmlhdGlvbiIpCgpwbG90LnRmcyA8LSBmdW5jdGlvbihwbG90LnRmcyl7CiAgYmluZF9yb3dzKGdleC5kZiwgbW1hdC5kZikgJT4lCiAgbGVmdF9qb2luKGRwdC5kZlssIGMoImNlbGwiLCAiZHB0X3BzZXVkb3RpbWUiLCAiYW5ub3RhdGlvbiIpXSwgYnk9ImNlbGwiKSAlPiUKICBtdXRhdGUoZHB0X3Jhbms9ZGVuc2VfcmFuayhkcHRfcHNldWRvdGltZSkpICU+JQogIGRyb3BfbmEoZHB0X3BzZXVkb3RpbWUpICU+JQogICAgbXV0YXRlKGRhdGE9ZmFjdG9yKGRhdGEsIGxldmVscz1jKCJHZW5lXG5leHByZXNzaW9uIiwgInNtb290aCIsICJNb3RpZlxuZGV2aWF0aW9uIikpKSAlPiUKICBmaWx0ZXIoZ2VuZSAlaW4lIHBsb3QudGZzKSAlPiUKICBnZ3Bsb3QoYWVzKGRwdF9yYW5rLCB2YWx1ZSwgY29sb3I9ZGF0YSkpICsKICAjIGdlb21fcG9pbnQoZGF0YT0uICU+JSBmaWx0ZXIoZGF0YSE9InNtb290aCIpLCBhZXMoY29sb3I9YW5ub3RhdGlvbiksIHNpemU9MC43LCBhbHBoYT0wLjMpICsKICBnZW9tX3Ntb290aCggbWV0aG9kPSJsb2VzcyIsc3Bhbj0wLjIpICsKICBmYWNldF9ncmlkKGRhdGF+Z2VuZSwgc2NhbGVzPSJmcmVlIikgKwogIHhsYWIoIlBzZXVkb3RpbWUgcmFuayIpICsKICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNikgCn0KCnRmcyA8LSBjKCJKVU4iLCAiRk9TTDIiLCAiRk9TTDEiLCAiRk9TIikKCnBkZihwYXN0ZTAob3V0ZGlyLCAiVEZfcGxvdHMucGRmIiksIHdpZHRoID0gOCwgaGVpZ2h0ID0gNSkKZm9yICh0ZiBpbiB0Zi50b3B2YXIpIHsKICBwcmludChwbG90LnRmcyh0ZikpCn0KZGV2Lm9mZigpCgptYXAobGlzdCgiSlVOIiwgIkVMSzMiLCAiUlVOWDIiLCAiUkVMIiwgIkZPUyIsICJFVFY2IiwgIlRDRjMiKSwgfiBwbG90LnRmcygueCkgKyBnZ3NhdmUocGFzdGUwKG91dGRpciwgcGFzdGUwKCdURl9wbG90XycsLngsIi5wbmciKSksIHdpZHRoID0gOCwgaGVpZ2h0PTUpKQpgYGAKCmBgYHtyLCBmaWcuaGVpZ2h0PTEyLCBmaWcud2lkdGg9Nn0KdGZzIDwtIGMoIlJVTlgyIiwgIkVMSzMiLCJKVU4iLCAiRVRWNiIpCnBsb3QudGZzKHRmcykKCnRmLmxpc3QgPC0gbWFwKHRmcywgfiBwbG90LnRmcygueCkpCnRmLmxpc3QgPC0gbWFwKHRmLmxpc3QsIH4gLnggKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpKQp0Zi5saXN0IDwtIG1hcF9pZih0Zi5saXN0LCBjKFRSVUUsIFRSVUUsIFRSVUUsIEZBTFNFKSwgfiAueCArIHRoZW1lKGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpLCBheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCkgKSkKCnRmLmxpc3QKcGxvdF9ncmlkKHBsb3RsaXN0ID0gdGYubGlzdCwgYWxpZ249InYiLCBheGlzPSJsIiwgbnJvdz00LCBuY29sPTEsIHJlbF9oZWlnaHRzID0gYygxLDEsMSwxLjE1KSkgKwogIGdnc2F2ZShwYXN0ZTAob3V0ZGlyLCAiVEZfcGxfZmlnLnBuZyIpLCBoZWlnaHQgPSAxMSwgd2lkdGggPSA1KQpgYGAKCiMjIyBXYXlzIHRvIG9wdGltaXplIHRoaXMgYW5hbHlzaXMgCgotIEltcHJvdmVkIG1vdGlmIGRhdGFiYXNlIChtb3JlIG1vdGlmcywgSmFzcGFyMjAyMCBvciBIT0NPTU9DTykKLSBDYWxsaW5nIHBlYWtzIG9uIGNsdXN0ZXJzICgpCgoKPCEtLSBgYGB7ciwgZmlnLndpZHRoPTE0LCBmaWcuaGVpZ2h0PTd9IC0tPgo8IS0tIGJpbmRfcm93cyhhY2Nlc3MuZGYsIGdlbmV4LmRmKSAlPiUgLS0+CjwhLS0gICBmaWx0ZXIoZ2VuZSAlaW4lIGMoIlNQSTEiLCAiUlVOWDIiLCJSVU5YMyIsICdUQ0Y3TDInLCAiRTJGNCIpKSAlPiUgLS0+CjwhLS0gICAgIGZpbHRlcighc3RyX2RldGVjdChjZWxsLCAiXkFUQUNfIikgfCB0ZWNoPT0iQVRBQyIpICU+JSAtLT4KPCEtLSAgICMgZ3JvdXBfYnkodGVjaCwgZ2VuZSkgJT4lIC0tPgo8IS0tICAgIyBtdXRhdGUodmFsdWU9c2NhbGUodmFsdWUpKSAlPiUgLS0+CjwhLS0gICAjIHVuZ3JvdXAoKSAlPiUgLS0+CjwhLS0gICAjIGRyb3BfbmEoKSAlPiUgLS0+CjwhLS0gIyBmaWx0ZXIodGVjaD09IlJOQSIpICU+JSAtLT4KPCEtLSAgIGRyb3BfbmEoZHB0X2JpbikgJT4lIC0tPgo8IS0tICAgZ2dwbG90KGFlcyhkcHRfYmluLCB2YWx1ZSkpICsgLS0+CjwhLS0gICBnZW9tX3BvaW50KGFlcyhjb2xvcj1hbm5vdGF0aW9uKSwgYWxwaGE9MC4yKSArIC0tPgo8IS0tICAgZmFjZXRfZ3JpZCh0ZWNofmdlbmUsIHNjYWxlcyA9ICJmcmVlX3kiKSArIC0tPgo8IS0tICAgZ2VvbV9zbW9vdGgoKSAtLT4KCgo8IS0tIGBgYCAtLT4KCjwhLS0gYGBge3IsIGZpZy53aWR0aD0xNCwgZmlnLmhlaWdodD03fSAtLT4KPCEtLSBiaW5kX3Jvd3MoYWNjZXNzLmRmLCBnZW5leC5kZikgJT4lIC0tPgo8IS0tICAgZmlsdGVyKGdlbmUgJWluJSBjKCJFTEszIiwgIkpVTkIiLCAiRk9TIikpICU+JSAtLT4KPCEtLSAgIGZpbHRlcighc3RyX2RldGVjdChjZWxsLCAiXkFUQUNfIikgfCB0ZWNoPT0iQVRBQyIpICU+JSAtLT4KPCEtLSAgICMgZ3JvdXBfYnkodGVjaCwgZ2VuZSkgJT4lIC0tPgo8IS0tICAgIyBtdXRhdGUodmFsdWU9c2NhbGUodmFsdWUpKSAlPiUgLS0+CjwhLS0gICAjIHVuZ3JvdXAoKSAlPiUgLS0+CjwhLS0gICBkcm9wX25hKGRwdF9iaW4pICU+JSAtLT4KPCEtLSAgIGdncGxvdChhZXMoZHB0X2JpbiwgdmFsdWUpKSArIC0tPgo8IS0tICAgZ2VvbV9wb2ludChhZXMoY29sb3I9YW5ub3RhdGlvbiksIGFscGhhPTAuMikgKyAtLT4KPCEtLSAgIGZhY2V0X2dyaWQodGVjaH5nZW5lLCBzY2FsZXMgPSAiZnJlZV95IikgKyAtLT4KPCEtLSAgIGdlb21fc21vb3RoKCkgKyAtLT4KPCEtLSAgIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9Y2VsbC50eXBlLnBhbCkgLS0+CgoKPCEtLSBgYGAgLS0+Cgo8IS0tICMjIFBzZXVkb3RpbWUgbGFnIGJldHdlZW4gRFAoUSkgaW4gYWNjZXNzaWJpbGl0eSBhbmQgZ2VuZSBleHByZXNzaW9uIC0tPgo8IS0tIFRoZSBEUCAoUSkgY2x1c3RlciBpbiB0aGUgQVRBQyBjZWxscyBpcyBzY29yZWQgd2l0aCBoaWdoIGNvbmZpZGVuY2UgLS0+CjwhLS0gYGBge3J9IC0tPgo8IS0tIGRwcS5jb2VtYmVkIDwtIGNvZW1iZWRbLHdoaWNoKGNvZW1iZWQkYW5ub3RhdGlvbj09IkRQIChRKSIpXSAtLT4KCjwhLS0gRmVhdHVyZVBsb3QoY29lbWJlZCwgZmVhdHVyZT0icHJlZGljdGlvbi5zY29yZS5tYXgiKSAtLT4KPCEtLSBEaW1QbG90KGNvZW1iZWQsIGdyb3VwLmJ5ID0iYW5ub3RhdGlvbiIsIHNwbGl0LmJ5ID0gInRlY2giKSAtLT4KCjwhLS0gYGBgIC0tPgoKPCEtLSBgYGB7ciwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTZ9IC0tPgo8IS0tIGNjYS5vYmogPC0gdHJhbnNmZXIuYW5jaG9yc0BvYmplY3QubGlzdFtbMV1dIC0tPgo8IS0tIG5ldy5tZXRhZGF0YSA8LSBjb2VtYmVkQG1ldGEuZGF0YVssYygiYW5ub3RhdGlvbiIsICJ0ZWNoIiksIGRyb3A9Rl0gJT4lIHJvd25hbWVzX3RvX2NvbHVtbigpICU+JSAtLT4KPCEtLSAgIG11dGF0ZShyb3duYW1lPWlmZWxzZShzdHJfZGV0ZWN0KHJvd25hbWUsICJeUk5BIiksIHN0cl9jKHJvd25hbWUsICJfcmVmZXJlbmNlIiksIHN0cl9jKHJvd25hbWUsICJfcXVlcnkiKSkpICU+JSAtLT4KPCEtLSAgIGNvbHVtbl90b19yb3duYW1lcygpIC0tPgo8IS0tIGNjYS5vYmogPC0gQWRkTWV0YURhdGEoY2NhLm9iaiwgbmV3Lm1ldGFkYXRhKSAtLT4KPCEtLSBjY2Eub2JqQG1ldGEuZGF0YSAtLT4KPCEtLSBEaW1QbG90KGNjYS5vYmosIGdyb3VwLmJ5PWMoImFubm90YXRpb24iLCJ0ZWNoIiksIHJlZHVjdGlvbiA9ICJjY2EiLCBkaW1zID0gMToyKSAtLT4KPCEtLSBEaW1QbG90KGNjYS5vYmosIGdyb3VwLmJ5PWMoImFubm90YXRpb24iLCJ0ZWNoIiksIHJlZHVjdGlvbiA9ICJjY2EiLCBkaW1zID0gMzo0KSAtLT4KPCEtLSBEaW1QbG90KGNjYS5vYmosIGdyb3VwLmJ5PWMoImFubm90YXRpb24iLCJ0ZWNoIiksIHJlZHVjdGlvbiA9ICJjY2EiLCBkaW1zID0gNTo2KSAtLT4KPCEtLSBgYGAgLS0+Cgo8IS0tIGBgYHtyLCBmaWcuaGVpZ2h0PTE4LCBmaWcud2lkdGg9MTh9IC0tPgo8IS0tIHRvcC5jYy5nZW5lcyA8LSBjY2Eub2JqQHJlZHVjdGlvbnMkY2NhLmwyQGZlYXR1cmUubG9hZGluZ3MgJT4lICAtLT4KPCEtLSAgIHJlc2hhcGUyOjptZWx0KHZhcm5hbWVzPWMoImdlbmUiLCAiQ0MiKSkgJT4lIC0tPgo8IS0tICAgZ3JvdXBfYnkoQ0MpICU+JSAtLT4KPCEtLSAgIG11dGF0ZShyYW5rPXJhbmsoYWJzKHZhbHVlKSkpICU+JSAtLT4KPCEtLSAgIHVuZ3JvdXAoKSAlPiUgLS0+CjwhLS0gICBmaWx0ZXIocmFuayA+IChtYXgocmFuayktMTApKSAlPiUgLS0+CjwhLS0gICBwdWxsKGdlbmUpICU+JSAtLT4KPCEtLSAgIHVuaXF1ZSgpIC0tPgoKPCEtLSBhdGFjLm1hdCA8LSBjb2VtYmVkQGFzc2F5cyRBVEFDQGRhdGEgLS0+CjwhLS0gcm5hLm1hdCA8LSBjb2VtYmVkQGFzc2F5cyRSTkFAZGF0YSAtLT4KCjwhLS0gYXRhYy5tYXRbZ2VuZS5vaSxdICU+JSAtLT4KPCEtLSAgIHsuWyx3aGljaChhcHBseSguLDIsIGZ1bmN0aW9uKHgpIHN1bSh4KSE9MCkpXX0gJT4lIC0tPgo8IS0tICAgcGhlYXRtYXA6OnBoZWF0bWFwKHNob3dfY29sbmFtZXM9RiwgY2x1c3RlcmluZ19kaXN0YW5jZV9yb3dzID0gImNvcnJlbGF0aW9uIiwgLS0+CjwhLS0gICAgICAgICAgICAgICAgICAgICAgIGFubm90YXRpb25fY29sID0gY29lbWJlZEBtZXRhLmRhdGFbLCJhbm5vdGF0aW9uIiwgZHJvcD1GXSAtLT4KPCEtLSAgICAgICAgICAgICAgICAgICAgICApIC0tPgo8IS0tIGBgYCAtLT4KPCEtLSBgYGB7ciwgZmlnLmhlaWdodD0xMCwgZmlnLndpZHRoPTEwfSAtLT4KPCEtLSBkcHEuY2VsbHMgPC0gcm93bmFtZXMobmV3Lm1ldGFkYXRhW25ldy5tZXRhZGF0YSRhbm5vdGF0aW9uPT0iRFAgKFEpIixdKSAtLT4KPCEtLSBkcHEucXVlcnkuaXggPC0gd2hpY2godHJhbnNmZXIuYW5jaG9yc0BxdWVyeS5jZWxscyAlaW4lIGRwcS5jZWxscykgLS0+CjwhLS0gZHBxLnJlZi5peCA8LSB3aGljaCh0cmFuc2Zlci5hbmNob3JzQHJlZmVyZW5jZS5jZWxscyAlaW4lIGRwcS5jZWxscykgLS0+CjwhLS0gbmV3Lm1ldGFkYXRhICU+JSAtLT4KPCEtLSAgIHJvd25hbWVzX3RvX2NvbHVtbigpICU+JSAtLT4KPCEtLSAgIGZpbHRlcih0ZWNoPT0iQVRBQyIpIC0tPgo8IS0tIHRyYW5zZmVyLmFuY2hvcnNAYW5jaG9ycyAlPiUgLS0+CjwhLS0gICBhcy50aWJibGUoKSAlPiUgLS0+CjwhLS0gICBtdXRhdGUoY2VsbDE9dHJhbnNmZXIuYW5jaG9yc0ByZWZlcmVuY2UuY2VsbHNbY2VsbDFdKSAlPiUgLS0+CjwhLS0gICBtdXRhdGUoY2VsbDI9dHJhbnNmZXIuYW5jaG9yc0BxdWVyeS5jZWxsc1tjZWxsMl0pICU+JSAtLT4KPCEtLSAgIG11dGF0ZShhbm5vLmNlbGwxID0gbmV3Lm1ldGFkYXRhW2NlbGwxLCAnYW5ub3RhdGlvbiddKSAlPiUgLS0+CjwhLS0gICBtdXRhdGUoYW5uby5jZWxsMiA9IG5ldy5tZXRhZGF0YVtjZWxsMiwgJ2Fubm90YXRpb24nXSkgJT4lIC0tPgo8IS0tICAgIyBzcHJlYWQoY2VsbDIsIHNjb3JlKSAgLS0+CjwhLS0gICBnZ3Bsb3QoYWVzKHNjb3JlKSkgKyAtLT4KPCEtLSAgIGdlb21faGlzdG9ncmFtKCkgKyAtLT4KPCEtLSAgIHhsaW0oMCwxKSArIC0tPgo8IS0tICAgIyBnZW9tX3RpbGUoKSArIC0tPgo8IS0tICAgZmFjZXRfZ3JpZChhbm5vLmNlbGwxfmFubm8uY2VsbDIsIHNjYWxlcz0iZnJlZV95Iiwgc3BhY2U9ImZyZWUiLCBsYWJlbGxlciA9ICJsYWJlbF9ib3RoIikgIC0tPgo8IS0tIGBgYCAtLT4KCjwhLS0gPCEtLSBzaG93IGFuY2hvciBtYXQgLS0+IC0tPgoKPCEtLSA8IS0tIGBgYHtyLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9MTB9IC0tPiAtLT4KPCEtLSA8IS0tIGFuY2hvci5tYXQgPC0gdHJhbnNmZXIuYW5jaG9yc0BhbmNob3JzICU+JSAtLT4gLS0+CjwhLS0gPCEtLSAgIGFzLnRpYmJsZSgpICU+JSAtLT4gLS0+CjwhLS0gPCEtLSAgIG11dGF0ZShjZWxsMT10cmFuc2Zlci5hbmNob3JzQHJlZmVyZW5jZS5jZWxsc1tjZWxsMV0pICU+JSAtLT4gLS0+CjwhLS0gPCEtLSAgIG11dGF0ZShjZWxsMj10cmFuc2Zlci5hbmNob3JzQHF1ZXJ5LmNlbGxzW2NlbGwyXSkgJT4lIC0tPiAtLT4KPCEtLSA8IS0tICAgc3ByZWFkKGNlbGwyLCBzY29yZSkgJT4lIC0tPiAtLT4KPCEtLSA8IS0tICAgY29sdW1uX3RvX3Jvd25hbWVzKCdjZWxsMScpICU+JSAtLT4gLS0+CjwhLS0gPCEtLSAgIGFzLm1hdHJpeCgpIC0tPiAtLT4KCjwhLS0gPCEtLSBhbmNob3IubWF0ICU+JSAgLS0+IC0tPgo8IS0tIDwhLS0gICBpZmVsc2UoaXMubmEoLiksIDAsIC4pICU+JSAtLT4gLS0+CjwhLS0gPCEtLSAgIHBoZWF0bWFwOjpwaGVhdG1hcChzaG93X3Jvd25hbWVzID0gRiwgc2hvd19jb2xuYW1lcyA9IEYsIC0tPiAtLT4KPCEtLSA8IS0tICAgICAgICAgICAgICAgICAgICAgIGFubm90YXRpb25fY29sID0gbmV3Lm1ldGFkYXRhWywnYW5ub3RhdGlvbicsIGRyb3A9Rl0sIC0tPiAtLT4KPCEtLSA8IS0tICAgICAgICAgICAgICAgICAgICAgIGFubm90YXRpb25fcm93ID0gbmV3Lm1ldGFkYXRhWywnYW5ub3RhdGlvbicsIGRyb3A9Rl0sIC0tPiAtLT4KPCEtLSA8IS0tICAgICAgICAgICAgICAgICAgICAgIGFubm90YXRpb25fY29sb3JzID0gbGlzdChhbm5vdGF0aW9uPWNlbGwudHlwZS5wYWwpKSAtLT4gLS0+CjwhLS0gPCEtLSBgYGAgLS0+IC0tPgoKPCEtLSBgYGB7cn0gLS0+CjwhLS0gcHJlZC5zY29yZXMgPC0gY29sbmFtZXMoY29lbWJlZEBtZXRhLmRhdGEpICU+JSBzdHJfc3Vic2V0KCJwcmVkaWN0aW9uLnNjb3JlIikgIC0tPgo8IS0tIGNvZW1iZWRAbWV0YS5kYXRhICU+JSAtLT4KPCEtLSAgIGZpbHRlcih0ZWNoPT0iQVRBQyIpICU+JSAtLT4KPCEtLSAgIHNlbGVjdChjKCJhbm5vdGF0aW9uIiwgcHJlZC5zY29yZXMpKSAlPiUgLS0+CjwhLS0gICBwaXZvdF9sb25nZXIoY29scz0tYW5ub3RhdGlvbiwgbmFtZXNfdG8gPSAiY2xhc3MiKSAlPiUgLS0+CjwhLS0gICBtdXRhdGUoY2xhc3M9c3RyX3JlbW92ZShjbGFzcywgInByZWRpY3Rpb24uc2NvcmUuIikpICU+JSAtLT4KPCEtLSAgIGZpbHRlcihjbGFzcyE9Im1heCIpICU+JSAtLT4KPCEtLSAgIGdncGxvdChhZXMoYW5ub3RhdGlvbiwgdmFsdWUsIGZpbGw9Y2xhc3MpKSArIC0tPgo8IS0tICAgZ2VvbV9ib3hwbG90KCkgLS0+CjwhLS0gYGBgIC0tPgoKPCEtLSBgYGB7ciwgZmlnLndpZHRoPTEwfSAtLT4KPCEtLSBEZWZhdWx0QXNzYXkoZHBxLmNvZW1iZWQpIDwtICJSTkEiIC0tPgo8IS0tIGRwcS5jb2VtYmVkIDwtIFNjYWxlRGF0YShkcHEuY29lbWJlZCwgZmVhdHVyZXMgPSBpbnRlZ3JhdGVfZmVhdHVyZXNfdW5pb24pIC0tPgo8IS0tIGRwcS5jb2VtYmVkIDwtIFJ1blBDQShkcHEuY29lbWJlZCwgZmVhdHVyZXMgPSBpbnRlZ3JhdGVfZmVhdHVyZXNfdW5pb24pIC0tPgoKPCEtLSBwbG90X2dyaWQoIC0tPgo8IS0tICAgRGltUGxvdChkcHEuY29lbWJlZCwgZ3JvdXAuYnkgPSBjKCJ0ZWNoIiksIHJlZHVjdGlvbiA9ICJwY2EiKSAsIC0tPgo8IS0tICAgRmVhdHVyZVBsb3QoZHBxLmNvZW1iZWQsIGZlYXR1cmU9ImRwdF9wc2V1ZG90aW1lIiwgcmVkdWN0aW9uID0gInBjYSIpICsgc2NhbGVfY29sb3JfdmlyaWRpc19jKCkgIC0tPgo8IS0tICAgKSAtLT4KPCEtLSBgYGAgLS0+CjwhLS0gYGBge3J9IC0tPgo8IS0tIHRvcFBDMS5kZiA8LSBkcHEuY29lbWJlZEByZWR1Y3Rpb25zJHBjYUBmZWF0dXJlLmxvYWRpbmdzWywxLCBkcm9wPUZdICU+JSAtLT4KPCEtLSAgIGFzLnRpYmJsZShyb3duYW1lcz0iZ2VuZSIpICU+JSAtLT4KPCEtLSAgICMgYXJyYW5nZShQQ18xKSAtLT4KPCEtLSAgIG11dGF0ZShyYW5rPSBkZW5zZV9yYW5rKFBDXzEpKSAlPiUgLS0+CjwhLS0gICBtdXRhdGUobGFiZWw9aWZlbHNlKHJhbmsgPiAobigpLTEwKSB8IHJhbmsgPCAoMTApICwgZ2VuZSxOQSkpICAtLT4KCjwhLS0gdG9wUEMxLmRmICU+JSAtLT4KPCEtLSAgIGdncGxvdChhZXMocmFuaywgUENfMSkpICsgLS0+CjwhLS0gICBnZW9tX3BvaW50KCkrIC0tPgo8IS0tICAgZ2dyZXBlbDo6Z2VvbV90ZXh0X3JlcGVsKGRhdGE9LiAlPiUgZmlsdGVyKCFpcy5uYShsYWJlbCkpLCBhZXMobGFiZWw9bGFiZWwpKSAtLT4KPCEtLSBgYGAgLS0+CjwhLS0gYGBge3IsIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTh9IC0tPgo8IS0tIEZlYXR1cmVQbG90KGRwcS5jb2VtYmVkLCBmZWF0dXJlPXVuaXF1ZSh0b3BQQzEuZGYkbGFiZWwpWzE6Nl0sIHJlZHVjdGlvbiA9ICJ1bWFwIikgIC0tPgo8IS0tICAgc2NhbGVfY29sb3JfdmlyaWRpc19jKCkgLS0+CjwhLS0gYGBgIC0tPgoKPCEtLSBgYGB7ciwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTR9IC0tPgo8IS0tIGNjYS5vYmpAcmVkdWN0aW9ucyRjY2FAZmVhdHVyZS5sb2FkaW5ncyAlPiUgLS0+CjwhLS0gICByZXNoYXBlMiAtLT4KPCEtLSAgIHBoZWF0bWFwOjpwaGVhdG1hcCgpIC0tPgo8IS0tIERpbUhlYXRtYXAoY2NhLm9iaiwgcmVkdWN0aW9uID0gImNjYSIsIGFzc2F5cyA9ICJBVEFDIikgLS0+CjwhLS0gRmVhdHVyZVBsb3QoY2NhLm9iaiwgcmVkdWN0aW9uPSJjY2EiLCBmZWF0dXJlPWdlbmUub2kpIC0tPgoKPCEtLSBgYGAgLS0+CgoKPCEtLSA8IS0tIERpZmZlcmVudGlhbGx5IGV4cHJlc3NlZCBnZW5lcyBiZXR3ZWVuIFJOQSBhbmQgQVRBQyBEUChRKSBjZWxscyAtLT4gLS0+CjwhLS0gPCEtLSBgYGB7cn0gLS0+IC0tPgo8IS0tIDwhLS0gVmFyaWFibGVGZWF0dXJlcyhjb2VtYmVkKSA8LSBpbnRlZ3JhdGVfZmVhdHVyZXNfdW5pb24gLS0+IC0tPgo8IS0tIDwhLS0gZHBxLmRpZmYgPC0gRmluZE1hcmtlcnMoZHBxLmNvZW1iZWQsIGZlYXR1cmVzPVZhcmlhYmxlRmVhdHVyZXMoY29lbWJlZCksICAtLT4gLS0+CjwhLS0gPCEtLSAgICAgICAgICAgICAgICAgICAgICAgICBncm91cC5ieSA9ICJ0ZWNoIiwgaWRlbnQuMSA9ICJSTkEiLCBpZGVudC4yID0gIkFUQUMiKSAtLT4gLS0+Cgo8IS0tIDwhLS0gdG9wLmRpZmYgPC0gcm93bmFtZXMoZHBxLmRpZmZbMToxMCxdKSAtLT4gLS0+CjwhLS0gPCEtLSBGZWF0dXJlUGxvdChjb2VtYmVkLCBmZWF0dXJlcyA9IHRvcC5kaWZmWzE6M10sIHNwbGl0LmJ5ID0gInRlY2giLCBjb2xzID0gdmlyaWRpczo6dmlyaWRpcyhuPTEwMCksIHNsb3QgPSAic2NhbGUuZGF0YSIsIG1heC5jdXRvZmYgPSAxMCkgKyBnZ3RpdGxlKCJTdGFnZSBNYXJrZXJzIikgLS0+IC0tPgo8IS0tIDwhLS0gYGBgIC0tPiAtLT4KPCEtLSA8IS0tIGBgYHtyfSAtLT4gLS0+Cgo8IS0tIDwhLS0gVmxuUGxvdChjb2VtYmVkWywgY29lbWJlZCR0ZWNoPT0iUk5BIl0sIGZlYXR1cmVzID0gdG9wLmRpZmZbMV0sIGdyb3VwLmJ5ID0gImFubm90YXRpb24iLCBwdC5zaXplID0gMC4xLCBzcGxpdC5ieSA9ICd0ZWNoJykgLS0+IC0tPgo8IS0tIDwhLS0gVmxuUGxvdChjb2VtYmVkLCBmZWF0dXJlcyA9IGMoIkFRUDMiLCAiVFJCQzIiKSwgZ3JvdXAuYnkgPSAidGVjaCIpIC0tPiAtLT4KPCEtLSA8IS0tIGBgYCAtLT4gLS0+Cgo8IS0tIGBgYHtyLCBmaWcud2lkdGg9MTJ9IC0tPgo8IS0tIGF0YWMubWF0IDwtIGNvZW1iZWRAYXNzYXlzJEFUQUNAZGF0YSAtLT4KPCEtLSBybmEubWF0IDwtIGNvZW1iZWRAYXNzYXlzJFJOQUBkYXRhIC0tPgoKPCEtLSBnZW5lLm9pIDwtIHVuaXF1ZSh0b3BQQzEuZGYkbGFiZWwpICU+JSBzdHJfc3Vic2V0KCJUUkFWIikgLS0+CjwhLS0gZ2VuZS5vaSAtLT4KPCEtLSBhdGFjLmRmIDwtIGF0YWMubWF0W2dlbmUub2ksLCBkcm9wPUZdICU+JSAtLT4KPCEtLSAgIGFzLm1hdHJpeCgpICU+JSAtLT4KPCEtLSAgIHJlc2hhcGUyOjptZWx0KHZhcm5hbWVzPWMoImdlbmUiLCAiY2VsbCIpKSAlPiUgLS0+CjwhLS0gICBtdXRhdGUodGVjaD0iQVRBQyIpICU+JSAtLT4KPCEtLSAgIGxlZnRfam9pbihkcHQuZGYsIGJ5PWMoJ2NlbGwnLCAidGVjaCIpKSAgLS0+CjwhLS0gIyAlPiUgLS0+CjwhLS0gIyAgIGdyb3VwX2J5KHRlY2gsIGRwdF9iaW4sIGdlbmUpICU+JSAtLT4KPCEtLSAjICAgc3VtbWFyaXNlKGZyYWM9c3VtKHZhbHVlIT0wKS9uKCkpIC0tPgoKPCEtLSBybmEuZGYgPC0gcm5hLm1hdFtnZW5lLm9pLCwgZHJvcD1GXSAlPiUgLS0+CjwhLS0gICBhcy5tYXRyaXgoKSAlPiUgLS0+CjwhLS0gICByZXNoYXBlMjo6bWVsdCh2YXJuYW1lcz1jKCJnZW5lIiwgImNlbGwiKSkgJT4lIC0tPgo8IS0tICAgZmlsdGVyKHN0cl9kZXRlY3QoY2VsbCwgIlJOQV8iKSkgJT4lIC0tPgo8IS0tICAgbXV0YXRlKHRlY2g9IlJOQSIpICU+JSAtLT4KPCEtLSAgIGxlZnRfam9pbihkcHQuZGYsIGJ5PWMoJ2NlbGwnLCAidGVjaCIpKSAtLT4KCgo8IS0tIGFjYy5wbCA8LSBhdGFjLmRmICU+JSAtLT4KPCEtLSAgIGRyb3BfbmEoZHB0X2JpbikgJT4lIC0tPgo8IS0tICAgZ2dwbG90KGFlcyhkcHRfYmluLCBmaWxsPWFzLmZhY3Rvcih2YWx1ZSkpKSArIC0tPgo8IS0tICAgZ2VvbV9iYXIoKSAgLS0+CjwhLS0gICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9Y2VsbC50eXBlLnBhbCwgbmEudmFsdWU9ImdyZXk1MCIpIC0tPgo8IS0tIGFjYy5wbCAtLT4KPCEtLSBleC5wbCA8LSBybmEuZGYgJT4lIC0tPgo8IS0tICAgZmlsdGVyKGdlbmU9PWdlbmUub2kpICU+JSAtLT4KPCEtLSAgICMgbXV0YXRlKHZhbHVlPWlmZWxzZSh2YWx1ZT4wLDEsMCkpICU+JSAtLT4KPCEtLSAgICMgZmlsdGVyKHZhbHVlIT0wKSAlPiUgLS0+CjwhLS0gICBnZ3Bsb3QoYWVzKGRwdF9iaW4sIHZhbHVlKSkgKyAtLT4KPCEtLSAgICMgZ2VvbV92aW9saW4oYWxwaGE9MC4yKSArIC0tPgo8IS0tICAgIyBnZW9tX3BvaW50KCkgLS0+CjwhLS0gICAjIHNjYWxlX2ZpbGxfdmlyaWRpc19kKCkgLS0+CjwhLS0gICAjIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jZWxsLnR5cGUucGFsKSAtLT4KPCEtLSAgIGdlb21faml0dGVyKGFscGhhPTAuNSwgc2l6ZT0wLjUpICsgLS0+CjwhLS0gICBnZW9tX3Ntb290aCgpICAtLT4KPCEtLSAgIHhsaW0oMCw1MCkgLS0+CgoKPCEtLSBwbG90X2dyaWQoYWNjLnBsLCBleC5wbCwgbmNvbD0xLCBucm93PTIsIGFsaWduID0gInYiLCBheGlzLj0ibCIpIC0tPgo8IS0tIGBgYCAtLT4KCjwhLS0gYGBge3J9IC0tPgo8IS0tIGF0YWMuZGYgJT4lIC0tPgo8IS0tICAgZHJvcF9uYShkcHRfcHNldWRvdGltZSkgJT4lIC0tPgo8IS0tICAgZ3JvdXBfYnkoIGdlbmUsIGRwdF9iaW4pICU+JSAtLT4KPCEtLSAgICMgc3VtbWFyaXNlKGZyYWM9c3VtKHZhbHVlKS9uKCkpICU+JSAtLT4KPCEtLSAgIGdncGxvdChhZXMoZHB0X2JpbiwgdmFsdWUsIGNvbG9yPWdlbmUpKSArIC0tPgo8IS0tICAgIyBnZW9tX3BvaW50KCkgKyAtLT4KPCEtLSAgIGdlb21fc21vb3RoKCkgLS0+CjwhLS0gYGBgIC0tPgoKLS0tCgoKCgoKCgo=